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

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

Этот код:

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

... приводит к этому исключению:

Значение не может быть нулевым. Имя параметра: клавиша

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

[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 допускает тип с нулевым значением для ключа, но не допускает нулевого значения?

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Вот почему .Net framework не позволяет null значение и выдает исключение.

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

if (key == null)

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

Ответ 10

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

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

Ответ 11

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


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

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

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

поэтому после запуска:

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

str1 и str2 фактически указывают на то же место в памяти! что делает их одинаковыми; который позволяет словарю найти элемент, добавленный с помощью 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 для его поиска.