Dictionary<TKey, TValue>はキーとしてnullを許容しない

[`Dictionary<TKey, TValue>`](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.7.2)の公式ドキュメント

公式ドキュメントの[注釈](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.7.2#%E6%B3%A8%E9%87%88)より

> A key cannot be null, but a value can be, if its type TValue is a reference type.

`Dictionary<TKey, TValue>`において、キーとして`null`は使えません。（参照型の場合。値型はそもそもnullにできない。）

* [Addメソッド](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.dictionary-2.add?view=netframework-4.7.2#System_Collections_Generic_Dictionary_2_Add__0__1_)
* [ContainsKeyメソッド](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.dictionary-2.containskey?view=netframework-4.7.2#System_Collections_Generic_Dictionary_2_ContainsKey__0_)
* [Itemプロパティ(インデクサ)](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.dictionary-2.item?view=netframework-4.7.2#System_Collections_Generic_Dictionary_2_Item__0_)

を使う際、nullをキーとして呼び出すと、`ArgumentNullException`が投げられます。

これは、それらのメソッドの内部でキーの`GetHashCode`メソッドを呼び出す必要があるためです。

次のような[コレクション初期化子](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers)は内部で、Addメソッドを使っているので、コレクション初期化子でも例外が発生することに注意してください。

```csharp
// 例外が発生する
var dict = new Dictionary<string, int> {
   { "a", 0 },
   { "b", 0 },
   { "c", 0 },
   { null, 0 },    
};
```

---

さて、これは実装である「[`Dictionary<TKey, TValue>`](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.7.2)」の仕様です。

* [`IDictionary<TKey, TValue>`](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.idictionary-2?view=netframework-4.7.2)
* [`IReadOnlyDictionary<TKey, TValue>`](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ireadonlydictionary-2?view=netframework-4.7.2)

のリファレンスには、

> Implementations can vary in whether they allow you to specify a key that is null.

とあります。

キーとしてnullを許可するかどうかは、`IDictionary<TKey, TValue>`と`IReadOnlyDictionary<TKey, TValue>`を実装するクラスのその実装に任されています。

キーとしてnullを許容する`IReadOnlyDictionary<TKey, TValue>`を実装するクラスの例を次に示します。


```csharp
public class NullableKeyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
    private readonly IReadOnlyDictionary<TKey, TValue> source;
    private readonly bool hasNullKey;
    private readonly TValue nullValue;

    public NullableKeyDictionary(IReadOnlyDictionary<TKey, TValue> source)
    {
        if (this.source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        this.source = source;
        this.hasNullKey = false;
        this.nullValue = default(TValue);
    }

    public NullableKeyDictionary(IReadOnlyDictionary<TKey, TValue> source, TValue nullValue)
    {
        if (this.source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        this.source = source;
        this.hasNullKey = true;
        this.nullValue = nullValue;
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        foreach (var it in this.source)
        {
            yield return it;
        }

        if (this.hasNullKey)
        {
            yield return new KeyValuePair<TKey, TValue>(default(TKey), nullValue);
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public int Count => this.hasNullKey ? this.source.Count + 1 : this.source.Count;

    public bool ContainsKey(TKey key)
    {
        if (this.hasNullKey && key == null)
        {
            return true;
        }
        else
        {
            return this.source.ContainsKey(key);
        }
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        if (this.hasNullKey && key == null)
        {
            value = nullValue;
            return true;
        }
        else
        {
            return this.TryGetValue(key, out value);
        }
    }

    public TValue this[TKey key] => key == null && hasNullKey
        ? this.nullValue
        : this.source[key];

    public IEnumerable<TKey> Keys
    {
        get
        {
            foreach (var it in this.source)
            {
                yield return it.Key;
            }

            if (this.hasNullKey)
            {
                yield return default(TKey);
            }
        }
    }

    public IEnumerable<TValue> Values
    {
        get
        {
            foreach (var it in this.source)
            {
                yield return it.Value;
            }

            if (this.hasNullKey)
            {
                yield return nullValue;
            }
        }
    }
}
```