PolyDictionary II: Constructed Keys

In the last entry we defined a dictionary that can contain a value of any type and extra that value out of the dictionary without our code using a cast. The dictionary itself used a cast (which I show a "way" to remove it next time) but our code didn't. One problem it had, however, is it used declared keys instead of constructed keys. A declared key is a key can only be referenced by knowing the variable it is assigned to; a unique object. A constructed key, on the other hand, uses object equality instead of object identity for the key. String keys are constructed keys; you can read them from a file, calculate them or use a literal value. The dictionary will eventually compare the strings for equality. In other words, constructed keys can be constructed from data where declared keys cannot.

Can we modify PolyDictionary to use constructed keys? One way to do it is to create a new Key type that takes the constructed key type as a parameter. One such type is,

    struct Key<K, V> {
        public K Value;
        public Key(K value) {
            this.Value = value;
        }
        public static implicit operator Key<K, V>(K value) {
            return new Key<K, V>(value);
        }
    }

This struct just wraps the key value. I use a struct instead of a class because structs will do a value comparison for equality by default, classes use reference equality.

The existing methods of PolyDictionary do not take this kind of key; but, we can add methods to PolyDictionary that do. For example, we can create an Add() method that looks like,

    public void Add<K, V>(Key<K, V> key, V value);

We can then create a Get() method that looks like,

    public V Get<K, V>(Key<K, V> key);

All these methods can be added directly to PolyDictionary because, through method overloading, they don't interfere with the original, declared key versions.

Using this dictionary looks like,

    Key<string, string> k1 = "k1";
    Key<string, int> k2 = "k2";
    Key<int, double> k3 = 3;

    PolyDictionary dictionary = new PolyDictionary();

    dictionary.Add(k1, "one");
    dictionary.Add(k2, 2);
    dictionary.Add(k3, 3.33);

    string v1 = dictionary.Get(k1);
    int v2 = dictionary.Get(k2);
    double v3 = dictionary.Get(k3);

    Console.WriteLine("v1 = {0}", v1);
    Console.WriteLine("v2 = {0}", v2);
    Console.WriteLine("v3 = {0}", v3);

The complete example of PolyDictionary follows,

    class PolyDictionary {
        private Dictionary<object, object> _table;

        public PolyDictionary() {
            _table = new Dictionary<object, object>();
        }

        public void Add<K, V>(Key<K, V> key, V value) {
            _table.Add(key, value);
        }

        public void Add<T>(Key<T> key, T value) {
            _table.Add(key, value);
        }

        public bool Contains<K, V>(Key<K, V> key) {
            return _table.ContainsKey(key);
        }

        public bool Contains<T>(Key<T> key) {
            return _table.ContainsKey(key);
        }

        public void Remove<K, V>(Key<K, V> key) {
            _table.Remove(key);
        }

        public void Remove<T>(Key<T> key) {
            _table.Remove(key);
        }

        public bool TryGetValue<K, V>(Key<K, V> key, out V value) {
            object objValue;
            if (_table.TryGetValue(key, out objValue)) {
                value = (V)objValue;
                return true;
            }
            value = default(V);
            return false;
        }

        public bool TryGetValue<T>(Key<T> key, out T value) {
            object objValue;
            if (_table.TryGetValue(key, out objValue)) {
                value = (T)objValue;
                return true;
            }
            value = default(T);
            return false;
        }

        public V Get<K, V>(Key<K, V> key) {
            return (V)_table[key];
        }

        public T Get<T>(Key<T> key) {
            return (T)_table[key];
        }

        public void Set<K, V>(Key<K, V> key, V value) {
            _table[key] = value;
        }

        public void Set<T>(Key<T> key, T value) {
            _table[key] = value;
        }
    }

Note: the implementation changed a bit from the previous example. The Get() methods now use the this property instead of calling TryGetValue(). This allows the Get() methods to be inlined at the cost of an additional cast or two in the code.

03/21/2007 8:25 PM

Hi,

I created just such a dictionary for a project a few months back, I believe I called it MixedDataDictionary, and I think it actually is nearly identical to what you have here. Perhaps we could compare notes? Send me an email if you're interested.

Tim

Tim | mailto:trickytAT NOSPAMgmail dot com | trickytAT NOSPAMgmail dot com

03/22/2007 4:11 AM

Ah, sorry I didn't see your second post before commenting the first post ;).

Now you have fixed the missing value issue.

But you still have to either keep the original key instances or create new key instsances and know what value type to create them with. For each key, all client code that wants to lookup by that key has to know the type that is stored for that key.

And if the client guesses wrong, he still gets a runtime cast error - this time inside the PolyDictionary class.

Finally, isn't the default structure GetHashCode and Equals methods very slow - and rely on Reflection? Would probably be better to override them and forward to the value instance?

Hallvard Vassbotn | http://hallvards.blogspot.com/

03/22/2007 9:27 AM

This dictionary is not usable in all situations that Hashtable is used. It is only usable when the builder of the dictionary and the user of the dictionary agree on the type of the data and the user doesn't have to guess the type. If the client is guessing as to the type, use a Hashtable directly.

This is most useful in situations where a dictionary is used to model a sparse record. The key takes the place of the field name, the type is, well the type. PolyDictionary enables a type-checked sparce record.

As for efficiency you are right. I chose brevity over efficiency. To make this production you would want to add an Equals() and GetHashCode(). If performance is a great concern you can sacrifice some safety to get better perf by using Value as the key in the underlying dictionary instead of key itself.

Chuck Jazdzewski | www.removingalldoubt.com | chuckjazAT NOSPAMhotmail dot com

03/23/2007 1:56 PM

Thanks for explaining the use-case.

Dave

01/01/2012 2:32 AM

At last! Someone with the insghit to solve the problem!

Lottie | http://www.yahoo.com/ | jnjianuoAT NOSPAM163 dot com

Add New

Name

Email

Homepage

Security Word

Type in the security Word

Content (HTML not allowed)