Почему вы не можете использовать null в качестве ключа для словаря?

видимо, вы не можете использовать null для ключа, даже если ваш ключ имеет тип nullable.

этот код:

var nullableBoolLabels = new System.Collections.Generic.Dictionary<bool?, string>
{
    { true, "Yes" },
    { false, "No" },
    { null, "(n/a)" }
};

...результаты в этом исключении:

значение не может быть null. Имя параметра: key

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

[ArgumentNullException: Value cannot be null. Parameter name: key] System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) +44 System.Collections.Generic.Dictionary'2.Insert(TKey key, TValue value, Boolean add) +40
System.Collections.Generic.Dictionary'2.Add(TKey key, TValue value) +13

почему .NET framework разрешает тип nullable для ключа, но не разрешает значение null?

11 ответов


он сказал бы вам то же самое, если бы у вас был Dictionary<SomeType, string>, SomeType будучи ссылочным типом, и вы пытались передать null как ключ, это не то, что влияет только на nullable тип, как bool?. Вы можете использовать любой тип в качестве ключа, nullable или нет.

все сводится к тому, что вы не можете действительно сравнить nulls. Я предполагаю, что логика не в состоянии поставить null в ключе свойство, которое предназначено для сравнения с другими объектами, заключается в том, что оно делает его бессвязно сравнивать null ссылки.

Если вам нужна причина из спецификаций, она сводится к "ключ не может быть нулевой ссылкой"на MSDN.

если вы хотите exemple возможного обходного пути, вы можете попробовать что-то похожее на нужна реализация IDictionary, которая позволит нулевой ключ


часто вам нужно вернуться к методологиям и методам C++, чтобы полностью понять, как и почему .NET Framework работает определенным образом.

в C++ вам часто приходится выбирать ключ, который не будет использоваться-словарь использует этот ключ для указания удаленных и / или пустых записей. Например, у вас есть словарь <int, int>, и после вставки записи вы удаляете ее. Вместо того, чтобы запускать очистку мусора прямо тогда и там, реструктуризация словаря, и приводит к плохой производительности; словарь просто заменит ключевое значение ключом, который вы ранее выбрали, в основном это означает: "когда вы пересекаете пространство памяти словаря, притворитесь, что это <key,value> пара не существует, не стесняйтесь перезаписывать ее."

такой ключ также используется в словарях, которые предварительно выделяют пространство в ведрах определенным образом - вам нужен ключ для "инициализации" ведер с вместо того, чтобы иметь флаг для каждой записи, который указывает, является ли его содержимое является действительным. Так что вместо тройного <key, value, initialized> ты бы кортежа!--3--> С правилом, что если key == empty_key, то он не был инициализирован - и поэтому вы не можете использовать empty_key в качестве допустимого значения ключа.

вы можете увидеть такое поведение в хеш-таблице Google (словарь для вас .NET people :) в документации здесь:http://google-sparsehash.googlecode.com/svn/trunk/doc/dense_hash_map.html

посмотреть set_deleted_key и set_empty_key функции, чтобы получить то, о чем я говорю.

Я бы поспорил, что .NET использует NULL как уникальный deleted_key или empty_key, чтобы делать такие изящные трюки, которые улучшают производительность.


вы не можете использовать нулевой bool? поскольку nullable типы предназначены для действия как ссылочные типы. Вы также не можете использовать нулевую ссылку в качестве словарного ключа.

причина, по которой вы не можете использовать нулевую ссылку в качестве словарного ключа, вероятно, сводится к дизайнерскому решению в Microsoft. Разрешение нулевых ключей требует их проверки, что делает реализацию медленнее и сложнее. Например, реализация должна была бы избегать использования .Равно или .GetHashCode на null ссылка.

Я согласен, что разрешение нулевых ключей было бы предпочтительнее,но теперь слишком поздно менять поведение. Если вам нужно обходное решение, вы можете написать свой собственный словарь с разрешенными нулевыми ключами или написать структуру-оболочку, которая неявно преобразуется в/из T и делает этот ключ типом для вашего словаря (т. е. структура будет обертывать null и обрабатывать сравнение и хэширование, поэтому словарь никогда не "видит" null).


нет фундаментальной причины. HashSet позволяет null, а HashSet-это просто словарь, в котором ключ имеет тот же тип, что и значение. Итак, действительно, нулевые ключи должны были быть разрешены, но будут ломаться, чтобы изменить его сейчас, поэтому мы застряли с ним.


Dictionairies (основное описание)
Словарь-это универсальная (типизированная) реализация класса Hashtable, представленная в .NET framework 2.0.

хэш-таблица хранит значение, основанное на ключе (более конкретно хэш ключа).
Каждый объект в .NET имеет метод GetHashCode.
Когда вы вставляете пару значений ключа в хэш-таблицу,GetHashCode вызывается на ключ.
Подумайте об этом: вы не можете назвать GetHashCode метод null.

так что насчет типов Nullable?
The Nullable class-это просто оболочка, позволяющая присваивать нулевые значения типам значений. В основном оболочка состоит из HasValue boolean, который сообщает, Является ли он нулевым или нет, и Value чтобы содержать значение типа value.

положил его вместе и что вы получите
.NET действительно не волнует, что вы используете в качестве ключа в хэш-таблице / словаре.
Но когда вы добавляете комбинация ключевых значений, она должна иметь возможность генерировать хэш ключа.
Не имеет значения, завернуто ли ваше значение в Nullable, null.GetHashCode является невозможным.

свойство индексатора и методы Add словаря проверят на null и выдадут исключение, когда оно найдет null.


Не использовать null является частью контракта в соответствии со страницей MSDN:http://msdn.microsoft.com/en-us/library/k7z0zy8k.aspx

Я думаю, причина в том, что наличие null в качестве допустимого значения просто усложнит код без причины.


значение ключа должно быть уникальным, поэтому null не может быть допустимым ключом, потому что null не указывает на ключ.

вот почему .Net framework не разрешить null значение и выбросить исключение.

что касается того, почему Nullable разрешено, а не ловит во время компиляции, я думаю, причина в том, что where предложение, которое позволяет everyt T кроме Nullable one невозможно (по крайней мере, я не знаю, как этого достичь).


Ах, проблемы общего кода. Рассмотрим этот блок через рефлектор:

private void Insert(TKey key, TValue value, bool add)
{
    int freeList;
    if (key == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
    }

нет способа переписать этот код, чтобы сказать: "Nullable nulls разрешены, но не позволяют ссылочным типам быть null".

хорошо, так как ab не позволяет TKey быть " bool?". Ну, опять же, в языке C# нет ничего, что позволило бы вам сказать это.


словари не могут принимать нулевые ссылочные типы по разным причинам, не в последнюю очередь потому, что у них нет метода GetHashCode.

значение null для типа nullable value предназначено для представления значения null-семантика должна быть как можно более синонимичной ссылочному null. Было бы немного странно, если бы вы могли использовать null nullable value, где вы не могли бы использовать нулевые ссылки только из-за детали реализации nullable value типы.

или словарь имеет:

if (key == null)

и они никогда не думали об этом.


ключи словаря не могут быть null в .NET, независимо от типа ключа (nullable или иначе).

из MSDN: пока объект используется в качестве ключа в словаре)>), он не должен меняться каким-либо образом, который влияет на его хэш-значение. Каждый ключ в словаре)>) должен быть уникальным в соответствии с компаратором равенства словаря. Ключ не может быть нулевой ссылкой( Nothing в Visual Basic), но значение может быть, если тип значения TValue ссылочный тип. (http://msdn.microsoft.com/en-us/library/xfhwa508.aspx)


Я только что читал об этом; и, как ответил Эрик, теперь я считаю, что это неправильно, не все строки автоматически интернируются, и мне нужно было переопределить операцию равенства.


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

Я был в мышлении vanilla C строки, просто являющейся массивом символов, поэтому мне потребовалось некоторое время, чтобы понять, почему строка, построенная конкатенацией, работала как ключ для поиска, в то время как массив байтов, встроенный в цикл, нет.

это потому, что внутренне .net присваивает все строки, содержащие одно и то же значение одной и той же ссылке. (это называется "стажировка")

Итак, после запуска:

{
string str1 = "AB";
string str2 = "A";
str1 += "C";
str2 += "BC";
}

str1 и str2 фактически указывают на одно и то же место в памяти! что делает их одинаковыми onject; что позволяет словарю найти элемент, добавленный с str1 в качестве ключа, используя str2.

а если ты:

{
char[3] char1;
char[3] char2;
char1[0] = 'A';
char1[1] = 'B';
char1[2] = 'C';
char2[0] = 'A';
char2[1] = 'B';
char2[2] = 'C';
}

char1 и char2-разные ссылки; если вы используете char1 для добавления элемента в словарь, вы не можете использовать char2 для его поиска.