Почему вы не можете использовать null в качестве ключа для словаря <bool ?, string>? - PullRequest
63 голосов
/ 01 февраля 2010

Очевидно, вы не можете использовать 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 Framework допускает тип ключа, допускающий значение NULL, но не допускает нулевое значение?

Ответы [ 11 ]

34 голосов
/ 01 февраля 2010

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

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

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

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

13 голосов
/ 01 февраля 2010

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

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

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

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

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

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

4 голосов
/ 01 февраля 2010

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

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

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

3 голосов
/ 07 апреля 2017

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

2 голосов
/ 06 марта 2010

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

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

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

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

Свойство Indexer и методы Add в словаре проверяют наличие нуля и выдают исключение при обнаружении нуля.

1 голос
/ 01 февраля 2010

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

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

0 голосов
/ 06 марта 2010

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


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

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

Это потому, что внутренне .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 для его поиска.

0 голосов
/ 01 февраля 2010

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

Нулевое значение для типа значения, допускающего значение NULL, предназначено для представления нулевого значения & ndash; семантика должна быть как можно более синонимичной с нулем ссылки. Было бы немного странно, если бы вы могли использовать значение NULL, равное NULL, если вы не можете использовать NULL ссылки только из-за деталей реализации типов значений NULL.

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

if (key == null)

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

0 голосов
/ 01 февраля 2010

Ах, проблемы универсального кода. Рассмотрим этот блок через Отражатель:

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

Невозможно переписать этот код, чтобы сказать «Допускаются пустые значения NULL, но недопустимы нулевые ссылочные типы».

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

0 голосов
/ 01 февраля 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...