21 March 2007

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.



blog comments powered by Disqus