Zero-key dictionary?

First, why Dictionary<TKey, TValue> does not support one blank key?

Secondly, is there an existing collection similar to a dictionary?

I want to keep System.Type "empty" or "missing" or "default", I thought that null would work well for this.




In particular, I wrote this class:

 class Switch { private Dictionary<Type, Action<object>> _dict; public Switch(params KeyValuePair<Type, Action<object>>[] cases) { _dict = new Dictionary<Type, Action<object>>(cases.Length); foreach (var entry in cases) _dict.Add(entry.Key, entry.Value); } public void Execute(object obj) { var type = obj.GetType(); if (_dict.ContainsKey(type)) _dict[type](obj); } public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases) { var type = obj.GetType(); foreach (var entry in cases) { if (entry.Key == null || type.IsAssignableFrom(entry.Key)) { entry.Value(obj); break; } } } public static KeyValuePair<Type, Action<object>> Case<T>(Action action) { return new KeyValuePair<Type, Action<object>>(typeof(T), x => action()); } public static KeyValuePair<Type, Action<object>> Case<T>(Action<T> action) { return new KeyValuePair<Type, Action<object>>(typeof(T), x => action((T)x)); } public static KeyValuePair<Type, Action<object>> Default(Action action) { return new KeyValuePair<Type, Action<object>>(null, x => action()); } } 

To include types. There are two ways to use it:

  • Statically. Just call Switch.Execute(yourObject, Switch.Case<YourType>(x => x.Action()))
  • precompiled. Create a switch and then use it later switchInstance.Execute(yourObject)

It works fine if you do not try to add the default case to the "precompiled" version (exception from the null argument).

+31
c #
Jan 08 2018-11-11T00:
source share
9 answers

It just struck me that the best answer is to simply track if a default case has been set:

 class Switch { private Dictionary<Type, Action<object>> _dict; private Action<object> defaultCase; public Switch(params KeyValuePair<Type, Action<object>>[] cases) { _dict = new Dictionary<Type, Action<object>>(cases.Length); foreach (var entry in cases) if (entry.Key == null) defaultCase = entry.Value; else _dict.Add(entry.Key, entry.Value); } public void Execute(object obj) { var type = obj.GetType(); if (_dict.ContainsKey(type)) _dict[type](obj); else if (defaultCase != null) defaultCase(obj); } ... 

The rest of the class will remain untouched.

+13
Jan 08 2018-11-11T00:
source share

1) Why : As described above, the problem is that the dictionary requires the implementation of the Object.GetHashCode() method. null has no implementation, so the hash code is not related.

2) Solution . I used a solution similar to the NullObject template, using generics, which allows you to easily use the dictionary (no other implementation of the dictionary is needed).

You can use it, for example:

 var dict = new Dictionary<NullObject<Type>, string>(); dict[typeof(int)] = "int type"; dict[typeof(string)] = "string type"; dict[null] = "null type"; Assert.AreEqual("int type", dict[typeof(int)]); Assert.AreEqual("string type", dict[typeof(string)]); Assert.AreEqual("null type", dict[null]); 

You just need to create this structure once in a lifetime:

 public struct NullObject<T> { [DefaultValue(true)] private bool isnull;// default property initializers are not supported for structs private NullObject(T item, bool isnull) : this() { this.isnull = isnull; this.Item = item; } public NullObject(T item) : this(item, item == null) { } public static NullObject<T> Null() { return new NullObject<T>(); } public T Item { get; private set; } public bool IsNull() { return this.isnull; } public static implicit operator T(NullObject<T> nullObject) { return nullObject.Item; } public static implicit operator NullObject<T>(T item) { return new NullObject<T>(item); } public override string ToString() { return (Item != null) ? Item.ToString() : "NULL"; } public override bool Equals(object obj) { if (obj == null) return this.IsNull(); if (!(obj is NullObject<T>)) return false; var no = (NullObject<T>)obj; if (this.IsNull()) return no.IsNull(); if (no.IsNull()) return false; return this.Item.Equals(no.Item); } public override int GetHashCode() { if (this.isnull) return 0; var result = Item.GetHashCode(); if (result >= 0) result++; return result; } } 
+13
Mar 07 '14 at 21:45
source share

It does not support it, because the dictionary hashes the key to determine the index, which it cannot do with a null value.

A quick fix would be to create a dummy class and enter a key value? dummyClassInstance. You will need additional information about what you are actually trying to do in order to give a less β€œhacky” fix.

+12
Jan 08 2018-11-11T00:
source share

NameValueCollection can accept a null key.

+6
Jan 08 2018-11-11T00:
source share

If you really need a dictionary that allows null keys, here is my quick implementation (not well written or well tested):

 class NullableDict<K, V> : IDictionary<K, V> { Dictionary<K, V> dict = new Dictionary<K, V>(); V nullValue = default(V); bool hasNull = false; public NullableDict() { } public void Add(K key, V value) { if (key == null) if (hasNull) throw new ArgumentException("Duplicate key"); else { nullValue = value; hasNull = true; } else dict.Add(key, value); } public bool ContainsKey(K key) { if (key == null) return hasNull; return dict.ContainsKey(key); } public ICollection<K> Keys { get { if (!hasNull) return dict.Keys; List<K> keys = dict.Keys.ToList(); keys.Add(default(K)); return new ReadOnlyCollection<K>(keys); } } public bool Remove(K key) { if (key != null) return dict.Remove(key); bool oldHasNull = hasNull; hasNull = false; return oldHasNull; } public bool TryGetValue(K key, out V value) { if (key != null) return dict.TryGetValue(key, out value); value = hasNull ? nullValue : default(V); return hasNull; } public ICollection<V> Values { get { if (!hasNull) return dict.Values; List<V> values = dict.Values.ToList(); values.Add(nullValue); return new ReadOnlyCollection<V>(values); } } public V this[K key] { get { if (key == null) if (hasNull) return nullValue; else throw new KeyNotFoundException(); else return dict[key]; } set { if (key == null) { nullValue = value; hasNull = true; } else dict[key] = value; } } public void Add(KeyValuePair<K, V> item) { Add(item.Key, item.Value); } public void Clear() { hasNull = false; dict.Clear(); } public bool Contains(KeyValuePair<K, V> item) { if (item.Key != null) return ((ICollection<KeyValuePair<K, V>>)dict).Contains(item); if (hasNull) return EqualityComparer<V>.Default.Equals(nullValue, item.Value); return false; } public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) { ((ICollection<KeyValuePair<K, V>>)dict).CopyTo(array, arrayIndex); if (hasNull) array[arrayIndex + dict.Count] = new KeyValuePair<K, V>(default(K), nullValue); } public int Count { get { return dict.Count + (hasNull ? 1 : 0); } } public bool IsReadOnly { get { return false; } } public bool Remove(KeyValuePair<K, V> item) { V value; if (TryGetValue(item.Key, out value) && EqualityComparer<V>.Default.Equals(item.Value, value)) return Remove(item.Key); return false; } public IEnumerator<KeyValuePair<K, V>> GetEnumerator() { if (!hasNull) return dict.GetEnumerator(); else return GetEnumeratorWithNull(); } private IEnumerator<KeyValuePair<K, V>> GetEnumeratorWithNull() { yield return new KeyValuePair<K, V>(default(K), nullValue); foreach (var kv in dict) yield return kv; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } 
+2
Jan 08 2018-11-11T00:
source share
+2
Oct 29 '14 at 1:59
source share

In your case, you are trying to use null as the sender value ("default") instead of actually storing null as the value. Instead of moving on to the problem of creating a dictionary that can accept null keys, why not just create your own sentinel value. This is a variation of the "null object template":

 class Switch { private class DefaultClass { } .... public void Execute(object obj) { var type = obj.GetType(); Action<object> value; // first look for actual type if (_dict.TryGetValue(type, out value) || // look for default _dict.TryGetValue(typeof(DefaultClass), out value)) value(obj); } public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases) { var type = obj.GetType(); foreach (var entry in cases) { if (entry.Key == typeof(DefaultClass) || type.IsAssignableFrom(entry.Key)) { entry.Value(obj); break; } } } ... public static KeyValuePair<Type, Action<object>> Default(Action action) { return new KeyValuePair<Type, Action<object>>(new DefaultClass(), x => action()); } } 

Please note that your first Execute function is significantly different from your second. Maybe you need something like this:

  public void Execute(object obj) { Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)_dict); } public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases) { Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)cases); } public static void Execute(object obj, IEnumerable<KeyValuePair<Type, Action<object>>> cases) { var type = obj.GetType(); Action<object> defaultEntry = null; foreach (var entry in cases) { if (entry.Key == typeof(DefaultClass)) defaultEntry = entry.Value; if (type.IsAssignableFrom(entry.Key)) { entry.Value(obj); return; } } if (defaultEntry != null) defaultEntry(obj); } 
+1
Jan 08 2018-11-11T00:
source share

EDIT: the real answer to the asked question: Why can't you use null as the key for the dictionary <bool ?, string>?

The reason the common dictionary doesn't support null is because TKey may be a value type that is not null.

 new Dictionary<int, string>[null] = "Null"; //error! 

To get one that does, you can either use a non-shared Hashtable (which uses object keys and values), or collapse your own using DictionaryBase .

Edit: just to find out why null is illegal in this case, consider this general method:

 bool IsNull<T> (T value) { return value == null; } 

But what happens when you call IsNull<int>(null) ?

 Argument '1': cannot convert from '<null>' to 'int' 

You get a compiler error, since you cannot convert null to int . We can fix this by saying that we only need null types:

 bool IsNull<T> (T value) where T : class { return value == null; } 

And, okay. The limitation is that we can no longer call IsNull<int> , since int not a class (object with a null value)

0
Jan 08 2018-11-11T00:
source share

The dictionary will hash the supplie key to get the index, in case of null the hash function cannot return a valid value, therefore it does not support zero in the key.

0
Jan 08 '11 at 8:01
source share



All Articles