Por que você não pode usar null como uma chave para um Dictionary ?

Aparentemente, você não pode usar um null para uma chave, mesmo que sua chave seja do tipo anulável.

Este código:

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

… resulta nesta exceção:

Valor não pode ser nulo. Nome do Parâmetro: key

Descrição: ocorreu uma exceção não tratada durante a execução da solicitação da web atual. Por favor, revise o rastreamento de pilha para obter mais informações sobre o erro e onde ele se originou no código.

[ArgumentNullException: Value cannot be null. Parameter name: 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

Por que a estrutura .NET permite um tipo anulável para uma chave, mas não permite um valor nulo?

Ele diria a mesma coisa se você tivesse um Dictionary , SomeType sendo um tipo de referência, e você tentasse passar null como a chave, não é algo que afeta apenas o tipo anulável como bool? . Você pode usar qualquer tipo como chave, anulável ou não.

Tudo se resume ao fato de que você não pode realmente comparar os nulls . Eu assumo a lógica por trás de não ser capaz de colocar null na chave, uma propriedade que é projetada para ser comparada com outros objects é que ela torna incoerente comparar referências null .

Se você quiser um motivo das especificações, ele se resume a uma “chave não pode ser uma referência nula” no MSDN .

Se você quiser um exemplo de uma possível solução alternativa, você pode tentar algo semelhante a precisar de uma implementação IDictionary que permitirá uma chave nula

Muitas vezes, você precisa voltar às metodologias e técnicas de C ++ para entender completamente como e por que o .NET Framework funciona de uma maneira específica.

Em C ++, você muitas vezes precisa escolher uma chave que não será usada – o dictionary usa essa chave para apontar para inputs excluídas e / ou vazias. Por exemplo, você tem um dictionary de e depois de inserir uma input, você a exclui. Em vez de executar a limpeza de lixo imediatamente, reestruturar o dictionary e levar a um mau desempenho; o dictionary replaceá apenas o valor KEY pela chave que você selecionou anteriormente, significando basicamente que “quando você está percorrendo o dictionary memoryspace, finja que este par não existe, sinta-se à vontade para sobrescrevê-lo.”

Essa chave também é usada em dictionarys que pré-alocam espaço em intervalos de uma maneira específica – você precisa de uma chave para “inicializar” os blocos com em vez de ter um sinalizador para cada input que indica se seu conteúdo é válido ou não. Então, ao invés de ter uma tripla você teria uma tupla com a regra sendo que se key == empty_key então ela não foi inicializada – e portanto você não pode usar empty_key como valor chave válido.

Você pode ver esse tipo de comportamento na hashtable do Google (dictionary para você, pessoas do .NET 🙂 na documentação aqui: http://google-sparsehash.googlecode.com/svn/trunk/doc/dense_hash_map.html

Olhe para as funções set_empty_key e set_empty_key para obter o que eu estou falando.

Eu aposto que o .NET usa o NULL como o delete_key exclusivo ou o empty_key para fazer esses truques bacanas que melhoram o desempenho.

Você não pode usar um bool nulo? porque os tipos anuláveis ​​devem atuar como tipos de referência. Você não pode usar uma referência nula como uma chave de dictionary.

O motivo pelo qual você não pode usar uma referência nula como uma chave de dictionary provavelmente se resume a uma decisão de design na Microsoft. Permitir chaves nulas requer checagem para elas, o que torna a implementação mais lenta e mais complicada. Por exemplo, a implementação teria que evitar o uso de .Equals ou .GetHashCode em uma referência nula.

Concordo que permitir chaves nulas seria preferível, mas é tarde demais para mudar o comportamento agora. Se você precisa de uma solução alternativa, você pode escrever seu próprio dictionary com as chaves nulas permitidas, ou você pode escrever uma estrutura wrapper implicitamente convertida para / de T e fazer com que o tipo de chave para seu dictionary (ie. lidar com comparação e hashing, então o dictionary nunca ‘vê’ o nulo).

Não há razão fundamental. HashSet permite nulo e um HashSet é simplesmente um dictionary onde a chave é do mesmo tipo que o valor. Então, realmente, chaves nulas deveriam ter sido permitidas, mas estariam quebrando para mudar isso agora, então estamos presos a isso.

Dicionários (descrição básica)
Um dictionary é a implementação genérica (digitada) da class Hashtable, introduzida no .NET framework 2.0.

Um hashtable armazena um valor com base em uma chave (mais especificamente um hash da chave).
Todo object no .NET tem o método GetHashCode .
Quando você insere um par de valores-chave em uma hashtable, GetHashCode é chamado na chave.
Pense nisso: você não pode chamar o método GetHashCode em null .

Então, e sobre os tipos Nullable?
A class Nullable é simplesmente um wrapper, para permitir que valores nulos sejam atribuídos a tipos de valor. Basicamente, o wrapper consiste em um booleano HasValue que informa se é nulo ou não, e um Value para conter o valor do tipo de valor.

Coloque tudo junto e o que você ganha
O .NET realmente não se importa com o que você usa como chave em um dictionary / hashtable.
Mas quando você adiciona uma combinação de valores-chave, ela precisa gerar um hash da chave.
Não importa se seu valor está dentro de um Nullable , null.GetHashCode é impossível.

A propriedade Indexer e os methods Add do Dictionary verificarão null e lançarão uma exceção quando encontrar null.

Não usar null faz parte do contrato de acordo com a página do MSDN: http://msdn.microsoft.com/en-us/library/k7z0zy8k.aspx

Eu acho que a razão é que ter nulo como valor válido só vai complicar o código sem motivo.

O valor da chave deve ser exclusivo, portanto, nulo não pode ser uma chave válida porque nulo não indica nenhuma chave.

É por isso que o framework .Net não permite valor nulo e lança uma exceção.

Quanto ao motivo pelo qual o Nullable permitia, e não captura em tempo de compilation, acho que a razão é porque aquela cláusula where que permite que todo T exceto o Nullable não seja possível (pelo menos eu não sei como conseguir isso).

Ah, os problemas do código genérico. Considere este bloco via refletor:

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

Não há como rewrite este código para dizer “Nulos anuláveis ​​são permitidos, mas não permitem que os tipos de referência sejam nulos”.

Ok, então como não deixar que o TKey seja um “bool”? Bem, novamente, não há nada na linguagem C # que permita que você diga isso.

Os dictionarys não podem aceitar tipos de referência nula por vários motivos, até porque eles não têm um método GetHashCode.

Um valor nulo para o tipo de valor anulável serve para representar um valor nulo – a semântica deve ser tão sinônimo quanto uma referência nula quanto possível. Seria um pouco estranho se você pudesse usar o valor null nullable onde você não poderia usar referências nulas apenas por causa de um detalhe de implementação de tipos de valor anuláveis.

Ou isso ou o dictionary tem:

 if (key == null) 

e eles nunca pensaram nisso.

As chaves do dictionary não podem ser nulas no .NET, independentemente do tipo de chave (anulável ou não).

Do MSDN: Contanto que um object seja usado como uma chave no Dictionary < (Of <(TKey, TValue>)>), ele não deve ser alterado de nenhuma maneira que afete seu valor de hash. Cada chave em um Dictionary < (Of <(TKey, TValue>)>) deve ser exclusiva de acordo com o comparador de igualdade do dictionary. Uma chave não pode ser referência nula (Nada no Visual Basic), mas um valor pode ser, se o tipo de valor TValue for um tipo de referência. ( http://msdn.microsoft.com/pt-br/library/xfhwa508.aspx )

Acabei de ler isso; e, como Eric respondeu, agora acredito que isso é incorreto, nem todas as strings são internadas automaticamente, e eu precisava replace a operação de igualdade.


Isso me mordeu quando eu converti um dictionary de usar uma string como uma chave para uma matriz de bytes.

Eu estava no código C da baunilha de uma string simplesmente sendo uma matriz de caracteres, então demorei um pouco para descobrir por que uma string construída pela concatenação funcionava como uma chave para pesquisas enquanto uma matriz de bytes construída em um loop não funcionava.

É porque internamente .net atribui todas as seqüências de caracteres que contêm o mesmo valor para a mesma referência. (é chamado de “Interno”)

então, depois de correr:

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

str1 e str2 na verdade apontam exatamente para o mesmo lugar na memory! o que os torna o mesmo object; que permite que um dictionary encontre um item adicionado com str1 como uma chave usando str2.

enquanto se você:

 { 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 e char2 são referências distintas; se você usar char1 para adicionar um item a um dictionary, não poderá usar o char2 para procurá-lo.