Непоследовательное поведение в словаре / IDictionary - PullRequest
4 голосов
/ 02 марта 2020

Взять следующий фрагмент кода

        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

Интересно, что доступ к элементу через индексатор приводит к двум различным поведениям в x и y. Как и в целом, интерфейсы должны предоставлять доступ к одной и той же внутренней реализации. но в этом случае все иначе.

в чем тут подвох?

Ответы [ 2 ]

6 голосов
/ 02 марта 2020

Это потому, что Dictionary<TKey, TValue> реализует как c версию generic *, так и c версию non-generi (просто IDictionary, которая дает Object).

Looking в источнике можно легко увидеть, что индексатор универсального c версии явно выбрасывает, когда ключ не найден:

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

Но не является универсальным c версия не:

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

Тогда, если вы явно не приведете свой Dictionary к не-универсальной c версии, вы вызываете индексатор, который выдает, когда ключ не найден.

Обновление (согласно вашему комментарию):

С тех пор как IDictionary (версия non-generi c) была введена в. NET 1.0 (когда еще не было поддержки дженериков), единственный тип, который он потреблял и возвращал, это, очевидно, System.Object (который можно проверить на null).

Следовательно, имеет смысл не бросать, когда ключ не найден, потому что можно просто сделать это:

dict["nonExistingKey"] != null

Но как только генерит c IDictionary<TKey, TValue> был введен, TValue также может быть установлен на тип значения (например, int или DateTime), и приведенный выше шаблон должен быть настроен на:

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

, который менее плавный, менее стандартный и более громоздкий в использовании.

С "точки зрения дизайна", я полагаю, они решили сохранить обратную совместимость для IDictionary, в то же время упреждающе выбрасывая для универсальной c версии.

2 голосов
/ 02 марта 2020

System.Collections.Generic.Dictionary<K,V> реализует множество интерфейсов.

Он реализует System.Collections.Generic.IDictionary<K,V>, который указывает, что доступ к несуществующим ключам с помощью свойства indexer должен выдавать.

Он также реализует System.Collections.IDictionary , который указывает, что доступ к несуществующим ключам с помощью свойства indexer должен возвращать ноль.

Индексатором по умолчанию для класса Dictionary является первый - с System.Collections.Generic.IDictionary<K,V>. Когда вы приводите IDictionary, вы говорите, что хотите второе поведение.

Также обратите внимание, что ((IDictionary)dictionary)["key"] возвращает объект, а dictionary["key"] возвращает int.

...