PolyDictionary

CLR's standard Dictionary<K,T> allows you to express the type of the key and the type value so you don't have to cast on the way in and out of a dictionary. For example you can cache a bunch of strings based on some integer key. I use this in the following program that takes an integer number and writes out the English equivalent.

    class Program {
        static Dictionary<int, string> names = new Dictionary<int, string>();

        static Program() {
            names[0] = "zero";
            names[1] = "one";
            names[2] = "two";
            names[3] = "three";
            names[4] = "four";
            names[5] = "five";
            names[6] = "six";
            names[7] = "seven";
            names[8] = "eight";
            names[9] = "nine";
            names[10] = "ten";
            names[11] = "eleven";
            names[12] = "twelve";
            names[13] = "thirteen";
            names[14] = "fourteen";
            names[15] = "fifteen";
            names[16] = "sixteen";
            names[17] = "seventeen";
            names[18] = "eighteen";
            names[19] = "nineteen";
            names[20] = "twenty";
            names[30] = "thirty";
            names[40] = "forty";
            names[50] = "fifty";
            names[60] = "sixty";
            names[70] = "seventy";
            names[80] = "eighty";
            names[90] = "ninety";
            names[100] = "hundred";
            names[1000] = "thousand";
            names[1000000] = "million";
            names[1000000000] = "billion";
        }

        static int WriteValue(int value, int place) {
            if (value >= place) {
                int subValue = value / place;
                WriteValue(subValue);
                Console.Write(" {0} ", names[place]);
                value = value % place;
            }
            return value;
        }

        static void WriteValue(int value) {
            int originalValue = value;
            if (value < 0) {
                value = -value;
                Console.WriteLine("negative ");
            }
            value = WriteValue(value, 1000000000);
            value = WriteValue(value, 1000000);
            value = WriteValue(value, 1000);
            value = WriteValue(value, 100);
            if (value > 20) {
                int subValue = (value / 10) * 10;
                Console.Write(names[subValue]);
                value = value % 10;
                if (value == 0)
                    return;
                Console.Write("-");
            }
            if (value != 0 || originalValue == 0)
                Console.Write(names[value]);
        }

        static void Main(string[] args) {
            while (true) {
                Console.Write("Enter number: ");
                string line = Console.ReadLine();
                if (line == "exit" || string.IsNullOrEmpty(line))
                    break;
                int value;
                if (int.TryParse(line, out value)) {
                    WriteValue(value);
                    Console.WriteLine();
                }
                else
                    Console.WriteLine("Error: expected number");
            }
        }
    }

This example doesn't use any casts. The expression names[10] is of type string and the index is of type int. This is great when the values of the dictionary are all homogeneous as name is above. But what if I want to store values of varying types but still not use a cast. In other words, I want a heterogeneous, type-checked dictionary. If I don't mind casts, I can just use a Hashtable as in,

    Hashtable table = new Hashtable();

    table.Add("k1", "one");
    table.Add("k2", 2);
    table.Add(3, 3.33);

    string v1 = (string)table["k1"];
    int v2 = (int)table["k2"];
    double v3 = (double)table[3];

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

Unfortunately, if I later change the code to,

    table.Add("k1", 1);
    table.Add("k2", 2);
    table.Add(3, 3.33);

    string v1 = (string)table["k1"];
    int v2 = (int)table["k2"];
    double v3 = (double)table[3];

The compiler will dutifully compile the code and I will get a runtime error at the first use of the dictionary when it tries to cast an integer to a string. I would like the compiler to catch this type of error instead of having to wait until my user does.

I have keys of varying type and values of varying type. How can I get this and type verification as well? In other words, what I want is something like,

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

    string v1 = dictionary[k1];
    int v2 = dictionary[k2];
    double v3 = dictionary[k3];

What I want to do is be able to infer the returns result of the expression dictionary[k1] from the key. In the above example, there should be something about the k1 key that tells the compiler that the result will be a string and when I change it later to an int, I want the compiler to catch the assignment to string as a type error.

To do this I need a method that can infer the type of the return result from a parameter. A simple example of such a method is,

    T NoOp(T value) { return value; }

which returns the same value of the same type as it retrieves for any value of any type. The return type is inferred from the parameter value's type.

This is not good enough yet. We don't want the key type to be the same as the value type; we want the value type to be arbitrary. To do this we need to encode the value's type in the key. One way is to have the key's type include the value's type as part of the key's type definition. This can be done through supplying a dummy type parameter, a type parameter that is not needed in the definition of the type but the dummy type parameter is carried around by the type and can be used by type inferencing. To do this we need a method something like,

    T Get<T>(Key<T> key) { ... }

which takes a key and extracts the return type from the keys definition. This requires a Key class definition that has one type parameter which is simply,

    class Key<T> { public Key() { } }

Note we don't use T in Key, it is just there so type inferencing will find it. We can now define some keys,

    Key<string> k1 = new Key<string>();
    Key<int> k2 = new Key<int>();
    Key<double> k3 = new Key<double>();

The keys are just arbitrary instances. Type inferencing will now be able to extract T from the key's type and use it as the return result! This means that Get(k1) will be  of type string and Get(k2) will be of type int.

Type inferencing also works the same way for other parameters of the method. For the Add() method we can define it as,

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

With this  Add() the code above will compile because the type of value is also inferred from the key!

Unfortunately, the this property does not accept type parameters so we cannot get the code,

    string v1 = dictionary[k1];
    int v2 = dictionary[k2];
    double v3 = dictionary[k3];

to compile; but if we replace the array indexers with a Get() method we can get close, as in,

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

This works if dictionary has a Get() method like we described above,

    public T Get<T>(Key<T> key);

There we have it! With the Add() and Get() methods we have the beginnings of a dictionary that can hold any value of any type and retrieve the value back without requiring our use of the dictionary to use a cast. A complete example is given below,

    class Key<T> { public Key() { } }

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

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

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

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

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

        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 T Get<T>(Key<T> key) {
            T value;
            if (!TryGetValue(key, out value))
                throw new KeyNotFoundException();
            return value;
        }

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

03/18/2007 7:38 PM

the ff code:
string v1 = dictionary.Get(k1);

will have to become the ff right?
string v1 = dictionary.Get<string>(k1);

bean

03/18/2007 7:41 PM

scrap my previous comment. your code works!

bean

03/22/2007 3:53 AM

Very nice and impressive use of the generic type system!

Two issues that I see though:

1) The Key object doesn't hold any key value? Shouldn't there either be a string key value or a generic K value in there? And the Key class would need to forward the GetHashCode and Equals methods to the value instance?

2) The Key object now knows the type of the value that is stored in the dictionary for that Key. But isn't the design wrong? The key shouldn't be burdened with this knowledge. Once you have created a Key object and used it to stuff some value into the dictionary, you have to keep hold of the Key value and use the same Key instance to do the lookup. But this is not how you typically use a dictionary. Instead you get an arbitrary string from an external source and lookup into the dictionary with that key.

I guess the two needs are incompatible with each other....? You can't have static typing and runtime lookup of arbitrary keys?

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

03/23/2007 1:50 PM

As the Key<T> class is defined without values, your dictionary is only able to perform lookups based on Type, not Type+Value.

An implementaion of Key<T> along the lines of Nullable<T> would, perhaps, be more compelling.

While it's a neat idea to allow the type of the key to influence the type of the return type and yet retain type safety, I'm curious why you choose to go this route instead of just using multiple dictionaries?



Dave

03/24/2007 6:54 PM

Hopefully PolyDictionary II address your concerns as well as the discussion answers the other questions you have. If not, let me know.

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

04/06/2007 1:49 AM

Funny, at the first sight you feel that you solved the problem of casting, but unforunaitly thats a false feeling.
You are passing the type of the value when you instantiate your key, so you could instead of new Key<V>() cast the result as (V)obj1;
so the knowledge of the type of the value still missing, and you still have to pass it someway (in casting or in instantiating the key).And the polyDictionary will do exactly the same if you call
Get<string>(obj1) and it will cast and return you a string, exactly as you asked for :)

Sadek Drobi | www.sadekdrobi.com | blogAT NOSPAMsadekdrobi dot com

04/07/2007 11:55 AM

The purpose of PolyDictionary is not to eliminate casts entirely, only in the use of the dictionary. This has significant value since the use of the dictionary is where casting problems occur and you want compiler help to detect them. Eliminating casting in the implementation would help performance but doesn't add to the safety of the class. Your example of Get<string>(obj1), though equivilent in performance, is not equivilent in safety.

Also, please read the follow-up entries to this one to see that I provide one (albiet awful) implemetnation of PolyDictionary that removes the cast entirely.

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

04/08/2007 4:44 AM

Sorry, but you are NOT adding safety!
what if I use two constructed keys with the same value but different type values? a runtime exception! right?
I guess your example can only work with declared keys, and in this case you should replace all the occurence of the objet in your program with the key class instance to have a type safety, which i guess is too much for using a polyDictionary :)

Sadek Drobi | www.sadekdrobi.com

04/08/2007 5:40 PM

I thought about this, which is why the constructed key is passed as a key instead of the value used to construct it. The key types have to be the same for them to compare equal. In other words, new Key<string,int>("foo") and Key<string,double>("foo") do not compare equal because they are different types even though they are both constructed with "foo". Since the key types must be the same the value types must be the same as well, hence the safety.

If this doesn't convince you I would be happy to look at an example that demonstrates what you mean. The Get<string>() example in your original post is insufficient because it doesn't compile.

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

04/08/2007 11:40 PM

Ok, and how will you discover that you passed the right type value when constructing your key? well at least an exception would tell me something is wrong in my example. in your case, i will just loose my object trieng to guess whether the key was int or long, or even string of int!
there is a type knowledge leaking, and the *safety* your are introducing is quite dangerous, and obliges you to go to debugger if you dont find the *Add* command in your own piece of code! But no hard feelings, I still like the idea, and I feel that it might be more usefull for another case than a PolyDictionary.

Sadek Drobi

04/09/2007 10:58 AM

I answered a similar question from Hallvard in response to the the PolyDictionary II entry. I don't intend this as a general replacement for a dictionary only in places where the builder and the reader both agree on the type and want compiler help to ensure, if it changes, or is used incorrectly, the compiler generates an error message. This occurs often in my code and I now use use this technique when it does. It is helpful to think of this as a sparse structure instead of a dictionary.

If the builder and the reader don't know apriori what the type is, PolyDictionary, as you rightly point out, is a terrible class to use. You might as well use a Hashtable.

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

Add New

Name

Email

Homepage

Security Word

Type in the security Word

Content (HTML not allowed)