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.