C# Dictionary<> 和可变键

13

据说C#规范将字符串设为不可变是为了避免哈希表在键引用更改字符串键内容时出现问题。

Dictionary<>类型允许使用引用类型作为键。字典如何避免导致“错位”值的修改键的问题?在使用对象作为键时是否进行成员逐一克隆?

6个回答

13
Dictionary<TKey,TValue>类型不会尝试防止用户修改使用的键。它完全取决于开发人员自己负责不改变键。
如果你仔细思考一下,这实际上是Dictionary<TKey,TValue>能够采取的唯一明智途径。考虑执行类似成员逐个克隆操作的影响。为了彻底,您需要进行深层克隆,因为键中引用的对象也可能被修改,从而影响哈希码。因此,现在表格中使用的每个键都必须克隆其完整的对象图以防止变异。这将是错误的,并且可能是非常昂贵的操作。

1
Python虽然采取了不同的方式,但是不允许可变数据作为缓存键。http://www.udacity.com/view#Course/cs212/CourseRev/apr2012/Unit/207010/Nugget/251006 - Eugeniu Torica

10

如果您使用可变的引用类型作为键,则 GetHashCode() 的默认实现将保证哈希相等性,无论对象状态如何(即哈希值与引用相关,而不是与状态相关)。 但是,您是正确的,对于具有值相等语义的可变类型(其中GetHashCode 可能依赖于状态),将其用作字典键是不好的选择。


这是一个好问题。谢谢你提醒我,对象的GetHashCode默认基于实例,我认为这可以节省开发人员很多时间。 - Eugeniu Torica
可变类类型与值相等语义似乎是个坏主意。虽然有一些例外(例如doubleDecimalList<T>.Enumerator等),但大多数.NET中的类型都实现了Equals(Object)以指示等价性;由于不同的可变类类型项永远不会等价,因此它们不应报告自己为EqualsList<T>.Enumerator的行为源于装箱和拆箱值类型具有不同的语义,但需要共享相同的Equals方法)。 - supercat

6
< p > Dictionary<> 类本身不会对可变键对象被更改进行保护。需要你自己知道你所使用的键类是否可变,并尽可能避免使用可变键类。


4
如果一个引用类型没有重写Equals/GetHashCode方法,使用默认比较器的字典将不会关心任何键对象的字段或属性,因此也不会注意或关心它们是否发生变化。最简单的方法是将默认的GetHashCode方法视为返回与“对象ID”相关联的数字,将默认的Equals方法视为比较“对象ID”。事实上,在一个限制在两十亿或更少对象的系统中,GetHashCode可以简单地返回一个对象ID,但出于各种原因,它可能也会执行其他操作。
如果Equals或GetHashCode检查的仅是对象ID的部分,则对于这些函数,所有对象都是不可变的。创建对象后,它将始终具有相同的ID,并且该ID将永远不会用于任何其他对象,直到先前对象ID的所有痕迹从宇宙中消失。

3
它不能避免这种情况。由调用代码来执行此操作:
只要将对象用作 Dictionary<TKey, TValue> 中的键,就必须确保它不会以任何影响其哈希值的方式更改。每个键在 Dictionary<TKey, TValue> 中必须根据字典的相等比较器是唯一的。如果值类型 TValue 是引用类型,则键不能为 null,但值可以为 null
(来自 MSDN

0
与所有答案相反,通过引入Record类,可以明确地为字典创建可变键。Record类将需要实现IEqualityComparer使用HashCode结构体。我可以举个例子。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接