C# Dictionary<> 和可变键

Posted

技术标签:

【中文标题】C# Dictionary<> 和可变键【英文标题】:C# Dictionary<> and mutable keys 【发布时间】:2011-03-01 17:46:57 【问题描述】:

有人告诉我,在 C# 规范中将字符串设为不可变的众多原因之一是为了避免 HashTable 在对字符串键的引用更改其内容时更改键的问题。

Dictionary 类型允许将引用类型用作键。字典如何避免导致“错位”值的更改键的问题?当用作键时,是否存在由对象组成的成员克隆?

【问题讨论】:

【参考方案1】:

如果引用类型不覆盖 Equals/GetHashCode,则使用默认比较器的字典不会关心任何关键对象的字段或属性,因此不会注意到或关心它们是否发生变化。最简单的方法是将默认的 GetHashCode 方法视为返回与“对象 ID”相关的数字,将默认的 Equals 方法视为比较“对象 ID”。实际上,在一个对象限制为 20 亿或更少的系统中,GetHashCode 可以简单地返回一个对象 ID,但由于各种原因,它也可能做其他事情。

如果由 Equals 或 GetHashCode 检查的对象的唯一部分是对象 ID,那么就这些函数而言,所有对象都是不可变的。一旦创建了一个对象,它将始终具有相同的 ID,并且在前一个对象 ID 的所有痕迹从 Universe 中消失之前,该 ID 将永远不会用于任何其他对象。

【讨论】:

【参考方案2】:

如果您使用可变引用类型作为键,GetHashCode() 的默认实现将保证哈希相等,而不管对象状态如何(即哈希与引用相关联,而不是状态)。但是,您是对的,具有值相等语义(其中 GetHashCode 可能取决于状态)的可变类型对于字典键来说是一个糟糕的选择。

【讨论】:

这个不错。感谢您记得我默认情况下对象的 GetHashCode 是基于实例的,我认为这可以为开发人员节省大量时间。 具有值相等语义的可变类类型似乎是个坏主意。虽然有一些例外(例如doubleDecimalList&lt;T&gt;.Enumerator 等),但.net 中的大多数类型都实现了Equals(Object) 以表示等价;由于不同的可变类类型项永远不会等价,因此它们永远不应将自己报告为EqualsList&lt;T&gt;.Enumerator 的行为源于装箱和未装箱的值类型具有不同的语义,但需要共享相同的 @987654328 @方法)。【参考方案3】:

它并没有避免这种情况。由调用代码来强制执行此操作:

只要将对象用作Dictionary&lt;TKey, TValue&gt; 中的键,它就不能以任何影响其哈希值的方式发生变化。根据字典的相等比较器,Dictionary&lt;TKey, TValue&gt; 中的每个键都必须是唯一的。键不能是null,但值可以是,如果值类型TValue 是引用类型。

(来自MSDN)

【讨论】:

【参考方案4】:

Dictionary&lt;TKey,TValue&gt; 类型不会尝试防止用户修改所使用的密钥。完全由开发人员负责不改变密钥。

如果您仔细考虑一下,这确实是唯一合理的方法 Dictionary&lt;TKey,TValue&gt;可以拿。考虑在对象上执行类似成员克隆的操作的含义。为了彻底,您需要进行深度克隆,因为键中引用的对象也可能发生变异,从而影响哈希码。因此,现在表中使用的每个键都克隆了完整的对象图,以防止突变。这将是错误的,并且可能是一项非常昂贵的操作。

【讨论】:

Python 虽然采用了另一种方式,并且不允许可变数据作为缓存键。 udacity.com/view#Course/cs212/CourseRev/apr2012/Unit/207010/…【参考方案5】:

Dictionary&lt;&gt; 类不会保护自己免受可变键对象的更改。由您决定您用作键的类是否是可变的,并尽可能避免它。

【讨论】:

以上是关于C# Dictionary<> 和可变键的主要内容,如果未能解决你的问题,请参考以下文章

编写字符串列表的不可变 F# 映射

在 C# 中使用非常大的 Dictionary<>

C#字典 Dictionary 的使用

C# 将 List<string> 转换为 Dictionary<string, string>

在 C# 中是不是有像 Dictionary<> 这样的类,但只有键,没有值?

C# Dictionary<>和HashTable中遍历Keys怎么写?