Почему вы не можете использовать 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

зачем .Чистая основа позволяют тип, допускающий значение null для ключа, но не допускает значение null?

11 60

11 ответов:

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

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

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

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

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

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

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

вы можете увидеть такое поведение в Google hashtable (словарь для вас .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 и ручкой сравнивать и хэширования, так что словарь никогда не увидит нуль).

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

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

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

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

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

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

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

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

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

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

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

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

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

нет никакого способа, чтобы переписать этот код, чтобы сказать "допускает значение null значение null разрешено, но не ссылочные типы, чтобы быть null".

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

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

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

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

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 для его поиска.