Need an IDictionary<TKey,TValue> implementation that will allow a null key
Asked Answered
R

8

19

Basically, I want something like this:

Dictionary<object, string> dict = new Dictionary<object, string>();
dict.Add(null, "Nothing");
dict.Add(1, "One");

Are there any built into the base class library that allow this? The preceding code will throw an exception at runtime when adding the null key.

Rovner answered 16/12, 2009 at 18:50 Comment(0)
S
12

You could avoid using null and create a special singleton value class that does the same thing. For example:

public sealed class Nothing
{ 
  public static readonly Nothing Value = new Nothing(); 
  private Nothing() {}
}

Dictionary<object, string> dict = new Dictionary<object, string>();
dict.add(Nothing.Value, "Nothing");
dict.add(1, "One");

This approach will fail to work if you intend to make your collection more strongly typed - let's say for example you want the key to be a string. Since string is sealed you can't inherit from it to create a "special value" substitute for null. Your alternatives become a bit more complicated. You could:

  1. Create some special constant value to represent the "empty" / "null" case. Kind of hacky and definitely a path to confusion. This can be a viable approach if the dictionary is completely private to some implementation class and you can write some Encode/Decode utility methods to avoid spreading the knowledge of how you translate keys all over the place.
  2. Create your own implementation of IDictionary that internally delegates to a Dictionary<> instance - except for the case of null. This violates the documented expectations for the IDictionary<> interface which does say that null keys should throw an exception. But you may be able to get away with it if it's the only way to solve your real problem. This only works if you own and create the dictionary instance.
  3. Find a way to solve your problem without storing a "null" key in the dictionary. For example, consider not populating the null key in the dictionary and having some special case logic to deal with it. Keys have to be hashable and comparable to work with the underlying implementation, which is why null is prohibited normally.

As an aside, does your dictionary key really need the key to be object? This can lead to subtle bugs due to reference equality being used where you may have intended Equals() to be evaluated as the basis for comparison.

Shaniqua answered 16/12, 2009 at 18:57 Comment(0)
M
4

How about this?

public class NullableDictionnary<T1, T2> : Dictionary<T1, T2>
{
    T2 null_value;

    public T2 this[T1 key]
    {
        get
        {
            if (key == null)
            { return null_value; }
            return base[key];
        }
        set
        {
            if (key == null)
            { null_value = value; }
            else
            { base[key] = value; }
        }
    }
}
Mellen answered 16/12, 2009 at 19:8 Comment(2)
Be wary of all the other functions you've now invalidated -- Count, Add, Clear, ContainsKey, ContainsValue, etc ... I mean, if you are just hacking, okay, but I dislike the idea of creating a "NullableDictionary" class if you are going to do that. When a hack is clearly a hack, you're less likely to get bit by it later.Alan
This doesn't compile - the required methods on Dictionary<TKey, TValue> are not virtual. You would have to do it as a separate adapter that implements IDictionary<TKey, TValue>.Sward
V
1

NameValueCollection can take a null key but it does not implement IDictionary. It would however be pretty easy to derive from DictionaryBase and provide Add/Remove/Indexers etc that simply replace null with something built in like:

class MyDictionary : DictionaryBase {
    private readonly object nullKey = new object();

    void Add(object key, string value) {
       if ( key == null ) { key = nullKey; }
       .. call base methods
    }

}
Veasey answered 16/12, 2009 at 19:0 Comment(0)
H
1

No need for a different implementation of Dictionary.

Take a look at my answer here: https://mcmap.net/q/120718/-why-doesn-39-t-dictionary-lt-tkey-tvalue-gt-support-null-key-duplicate

You will also be able to keep your dictionary strongly typed:

var dict = new Dictionary<NullObject<int?>, string>();
dict[1] = "one int";
dict[null] = "null int";

Assert.AreEqual("one int", dict[1]);
Assert.AreEqual("null int", dict[null]);
Historian answered 7/3, 2014 at 21:50 Comment(0)
A
1

You can simply use ValueTuple as a wrapper for key, for example:

Dictionary<ValueTuple<string?>, string>
Affinity answered 12/9, 2021 at 2:40 Comment(0)
J
1

If key is enum, you can use not existing value instead of null like (YourEnum)(-1)

Jarl answered 16/9, 2022 at 6:22 Comment(0)
E
0

Does the key literally need to be NULL? The key in the collection works out to be an index. It doesn't make a lot of sense to me to have NULL for an index in a collection.

Maybe create a new class


public class ObjectEntry
{
    public object objRef;
    public string desc;

    public ObjectEntry(object objectReference)
    {
        objRef = objectReference;
        if (objRef = null) {desc = "Nothing";}
        else {desc = objRef.Description;} //or whatever info you can get from a proper objRef value
    }
}

newObj = new ObjectEntry(null);
dict.add(newObj, newObj.desc);
Effendi answered 16/12, 2009 at 19:10 Comment(0)
M
-1

A slight variation on jestro's answer to make for a cleaner(to me) solution that makes it more explicit what you are trying to do. Obviously this could be extended as needed. But you get the picture, just make a wrapper.

public class NullDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    private TValue _default;
    public new TValue this[TKey key]
    {
        get { 
            if(key == null)
            {
                return _default;
            }
            return _decorated[key];
        }
    }
    private Dictionary<TKey, TValue> _decorated;
    public NullDictionary( Dictionary<TKey,TValue> decorate, TValue defaultValue = default)
    {
        _decorated = decorate;
        _default = defaultValue;
    }    

}
Mnemosyne answered 13/5, 2020 at 16:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.