为什么不能使用null作为Dictionary的一个键<bool?,string =“”>?</bool ?,>

时间:2010-02-01 04:40:11

标签: c# .net dictionary hashtable

显然,即使您的密钥是可以为空的类型,也不能使用null作为密钥。

此代码:

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

...导致此异常:

  

值不能为空。   参数名称:key

     

描述:执行当前Web请求期间发生了未处理的异常。请查看堆栈跟踪,以获取有关错误及其在代码中的起源位置的更多信息。

     

[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框架允许键的可空类型,但不允许空值?

11 个答案:

答案 0 :(得分:34)

如果你有一个Dictionary<SomeType, string>SomeType是一个引用类型,而你试图传递null作为关键字,那么它会告诉你同样的事情像bool?这样的可空类型。您可以使用任何类型作为密钥,可以为空或可以为空。

这一切都归结为你无法真正比​​较nulls。我假设无法在密钥中放置null的逻辑,设计用于与其他对象进行比较的属性是它使得比较null引用变得不连贯。

如果您需要来自规范的原因,可归结为“A键不能为空引用”on MSDN

如果您想要一个可能的解决方法的示例,您可以尝试类似于Need an IDictionary implementation that will allow a null key

的内容

答案 1 :(得分:13)

通常,您必须回到C ++方法和技术,以完全理解.NET Framework以特定方式工作的方式和原因。

在C ++中,您经常需要选择一个不会被使用的密钥 - 字典使用此密钥指向已删除和/或空的条目。例如,您有一个<int, int>的字典,在插入条目后,您将其删除。而不是当时和那里运行垃圾清理,重组字典,并导致糟糕的表现;字典只会用您之前选择的键替换KEY值,基本上意味着“当您遍历字典memoryspace时,假装此<key,value>对不存在,请随意覆盖它。”

这样的密钥也用在以特定方式在桶中预分配空间的字典中 - 您需要一个键来“初始化”桶,而不是每个条目都有一个标志,指示其内容是否为有效。因此,不是使用三元组<key, value, initialized>,而是有一个元组<key, value>,其规则是如果key == empty_key则它尚未初始化 - 因此您不能将empty_key用作有效的KEY值。

您可以在此处的文档中的Google哈希表(适用于.NET的人员:)中看到此类行为:http://google-sparsehash.googlecode.com/svn/trunk/doc/dense_hash_map.html

查看set_deleted_keyset_empty_key函数以获取我正在谈论的内容。

我打赌.NET使用NULL作为唯一的deleted_key或empty_key,以便做一些提高性能的漂亮技巧。

答案 2 :(得分:4)

你不能使用null bool?因为可以为空的类型就像引用类型一样。您也不能将空引用用作字典键。

您无法将空引用用作字典键的原因可能归结为Microsoft的设计决策。允许空键需要检查它们,这使得实现更慢,更复杂。例如,实现必须避免在空引用上使用.Equals或.GetHashCode。

我同意允许使用null键更好,但现在改变行为为时已晚。如果你需要一个解决方法,你可以使用允许的空键编写自己的字典,或者你可以编写一个包装器结构,隐式转换为/从T转换为字典的键类型(即结构将包装null和处理比较和散列,所以字典永远不会“看到”null。

答案 3 :(得分:3)

没有根本原因。 HashSet允许null,而HashSet只是一个Dictionary,其中键与值的类型相同。所以,实际上,应该允许使用null键,但现在要改变它,以便我们坚持使用它。

答案 4 :(得分:2)

Dictionairies(基本描述)
字典是Hashtable类的通用(类型)实现,在.NET framework 2.0中引入。

哈希表存储基于密钥的值(更具体地说是密钥的哈希值) .NET中的每个对象都有方法GetHashCode 将键值对插入哈希表时,会在键上调用GetHashCode 想一想:你不能在GetHashCode上调用null方法。

那么Nullable类型呢?
Nullable类只是一个包装器,允许将空值赋给值类型。基本上,包装器由一个HasValue布尔值组成,它告诉它是否为空,一个Value包含值类型的值。

把它放在一起,你得到什么
.NET并不关心你在哈希表/字典中使用什么键 但是当你添加一个键值组合时,它必须能够生成键的哈希值 如果您的值包含在Nullable内,null无关紧要.GetHashCode是不可能的。

Dictionary的Indexer属性和Add方法将检查null,并在找到null时抛出异常。

答案 5 :(得分:1)

根据MSDN页面http://msdn.microsoft.com/en-us/library/k7z0zy8k.aspx

,不使用null是合同的一部分

我想原因是将null作为有效值会使代码无缘无故地复杂化。

答案 6 :(得分:0)

键值必须是唯一的,因此null不能是有效键,因为null表示没有键。

为什么.Net框架允许 null 值并抛出异常。

至于为什么Nullable允许,而不是在编译时捕获,我认为原因是因为where条款允许每个T除了Nullable之外是不可能的(至少我不是我知道如何实现这一点。

答案 7 :(得分:0)

啊,通用代码的问题。通过Reflector考虑这个块:

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

没有办法重写此代码以说“允许Nullable空值,但不允许引用类型为null”。

好的,那么怎么不允许TKey成为“bool?”。再好吧,C#语言中没有任何内容可以让你说出来。

答案 8 :(得分:0)

由于各种原因,字典无法接受空引用类型,尤其是因为它们没有GetHashCode方法。

可空值类型的空值意味着表示空值 - 语义应尽可能与引用null同义。如果你可以使用null nullable值,那么你可以使用null引用只是因为可以为null的值类型的实现细节,这会有点奇怪。

该词典或词典有:

if (key == null)

他们从来没有真正考虑过它。

答案 9 :(得分:-1)

无论键的类型如何(可为空或其他),.NET中的字典键都不能为空。

来自MSDN:只要一个对象被用作Dictionary&lt;(Of&lt;(TKey,TValue&gt;)&gt;)中的一个键,它就不能以任何影响其哈希值的方式改变。根据字典的相等比较器,Dictionary&lt;(Of&lt;(TKey,TValue&gt;)&gt;)中的每个键必须是唯一的。如果值类型TValue是引用类型,则键不能为空引用(在Visual Basic中为Nothing),但值可以为。 (http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

答案 10 :(得分:-1)

我刚读过这篇文章;正如Eric回答的那样,我现在认为这是不正确的,并非所有字符串都自动Interned,我需要覆盖相等操作。


当我将字典从使用字符串作为键转换为字节数组时,这就是我。

我在一个字符串的vanilla C心态中只是一个字符数组,所以我花了一些时间来弄清楚为什么串联构建的字符串作为查找的关键字,而在循环中构建的字节数组不

这是因为内部.net将包含相同值的所有字符串分配给同一个引用。 (它被称为'实习')

所以,在跑完之后:

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

str1和str2实际上指向内存中完全相同的位置!这使它们成为同样的注入;它允许字典使用str2查找使用str1作为键添加的项目。

如果你:

{
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来查找它。