Take the following piece of code
var dictionary = new Dictionary<string, int>
{
["A"] = 1,
["B"] = 2,
["C"] = 3,
};
var dictionaryx = (IDictionary)dictionary;
var x = dictionaryx["X"]; // return null
var y = dictionary["Y"]; // throws KeyNotFoundException
It is interesting to see the element access through the indexer leads to two different behaviours in x
and y
.
As in general, interfaces should give access to the same internal implementation. but in this case, it is different.
what is the catch here?
That's because Dictionary<TKey, TValue>
is implementing both the generic version of IDictionary<TKey, TValue>
and the non-generic version (just IDictionary
that yields an Object
).
Looking at the source, you can easily see that the indexer of the generic version explicitly throws when the key is not found:
public TValue this[TKey key]
{
get
{
int i = FindEntry(key);
if (i >= 0) return entries[i].value;
ThrowHelper.ThrowKeyNotFoundException(); // <-- this line
return default(TValue);
}
}
But the non-generic version does not:
object IDictionary.this[object key]
{
get
{
if (IsCompatibleKey(key))
{
int i = FindEntry((TKey)key);
if (i >= 0)
return entries[i].value;
}
return null;
}
}
Then, unless you explicitly cast your Dictionary
to the non-generic version, you're calling the indexer that throws when the key is not found.
Update (as per your comment):
Since IDictionary
(the non-generic version) was introduced in .NET 1.0 (when there was no support for generics yet) the only type it consumed and returned is obviously System.Object
(which can be tested for null
).
Hence, it made sense not to throw when key is not found because one could simply do this:
dict["nonExistingKey"] != null
But, once the generic IDictionary<TKey, TValue>
was introduced, the TValue
can also be set to a value type (like int
or DateTime
) and the above pattern would have to be adjusted to:
var dict = new Dictionary<string, int>();
dict["nonExistingKey"] != 0;
dict["nonExistingKey"] != default(int);
Which is less fluent, less standard and more cumbersome to use.
From "design perspective", I presume they decided to preserve backward compatibility for IDictionary
while proactively throwing for the generic version.
System.Collections.Generic.Dictionary<K,V>
implements many interfaces.
It implements System.Collections.Generic.IDictionary<K,V>
, which specifies that access to non-existing keys using the indexer property should throw.
It also implements System.Collections.IDictionary
, which specifies that access to non-existing keys using the indexer property should return null.
The default indexer of the Dictionary class is the first one - from System.Collections.Generic.IDictionary<K,V>
. When you cast to IDictionary
, you say you want the second behaviour.
Also, note that ((IDictionary)dictionary)["key"]
returns an object, while dictionary["key"]
returns an int.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With