Inconsistent behaviour in Dictionary/IDictionary

Sen*_*ncy 4 .net c# dictionary

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
Run Code Online (Sandbox Code Playgroud)

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?

hai*_*770 6

那是因为Dictionary<TKey, TValue>同时实现了 的通用版本IDictionary<TKey, TValue>和非通用版本(只是IDictionary产生了一个Object)。

查看源代码,您可以轻松看到通用版本索引器在找不到键时显式抛出:

public TValue this[TKey key]
{
    get
    {
        int i = FindEntry(key);
        if (i >= 0) return entries[i].value;
        ThrowHelper.ThrowKeyNotFoundException(); // <-- this line
        return default(TValue);
    }
}
Run Code Online (Sandbox Code Playgroud)

非通用版本不会:

object IDictionary.this[object key]
{
    get
    {
        if (IsCompatibleKey(key))
        {
            int i = FindEntry((TKey)key);
            if (i >= 0)
                return entries[i].value;
        }
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,除非您明确地将您Dictionary的版本强制转换为非通用版本,否则您将调用在找不到键时抛出的索引器。

更新(根据您的评论):

由于IDictionary(非泛型版本)是在 .NET 1.0 中引入的(当时还不支持泛型),它消耗和返回的唯一类型显然是System.Object(可以对其进行测试null)。

因此,在找不到密钥时不抛出是有意义的,因为可以简单地这样做:

dict["nonExistingKey"] != null
Run Code Online (Sandbox Code Playgroud)

但是,一旦IDictionary<TKey, TValue>引入了泛型, theTValue也可以设置为值类型(如intDateTime),并且必须将上述模式调整为:

var dict = new Dictionary<string, int>();
dict["nonExistingKey"] != 0;
dict["nonExistingKey"] != default(int);
Run Code Online (Sandbox Code Playgroud)

哪个更不流畅,更不标准,使用起来更麻烦。

从“设计角度”来看,我认为他们决定保留向后兼容性,IDictionary同时主动推出通用版本。