有人告诉我,在 C# 规范中将字符串设置为不可变的众多原因之一是为了避免当对字符串键的引用更改其内容时,哈希表的键发生更改的问题。
Dictionary<> 类型允许引用类型用作键。 字典如何避免更改键导致值“放错位置”的问题? 当用作键时,是否存在由对象组成的成员克隆?
Dictionary<TKey,TValue>
类型不会尝试防止用户修改所使用的密钥。 不改变密钥完全由开发人员负责。
如果您仔细考虑一下,这确实是唯一明智的路线
Dictionary<TKey,TValue>
可以拿。 考虑在对象上执行诸如成员克隆之类的操作的含义。 为了彻底,您需要进行深度克隆,因为密钥中引用的对象也可能发生变异,从而影响哈希码。 因此,现在表中使用的每个键都克隆了完整的对象图,以防止突变。 这不仅有问题,而且可能是一个非常昂贵的操作。
如果您使用可变引用类型作为键,则
GetHashCode()
的默认实现将保证散列相等,无论对象状态如何(即散列与引用相关联,而不是与状态相关联)。 然而,你是对的,具有值相等语义的可变类型(其中 GetHashCode 可能取决于状态)对于字典键来说是一个糟糕的选择。
Dictionary<>
类没有采取任何措施来保护自身免受可变密钥对象被更改的影响。您需要知道用作键的类是否可变,并尽可能避免它。
如果引用类型不重写 Equals/GetHashCode,则使用默认比较器的字典将不会关心任何关键对象的字段或属性,因此不会注意到或关心它们是否发生更改。 最简单的做法是,将默认的 GetHashCode 方法视为返回与“对象 ID”相关的数字,将默认的 Equals 方法视为比较“对象 ID”。 事实上,在对象数量限制为 20 亿或更少的系统中,GetHashCode 可以简单地返回对象 ID,但由于各种原因它也可能执行其他操作。
如果 Equals 或 GetHashCode 检查的对象的唯一部分是对象 ID,则就这些函数而言,所有对象都是不可变的。 一旦创建了一个对象,它将始终具有相同的 ID,并且该 ID 永远不会用于任何其他对象,直到前一个对象 ID 的所有痕迹从宇宙中消失为止。
它并不能避免这种情况。由调用代码来强制执行此操作:
只要一个对象用作
中的键,它就不能以任何影响其哈希值的方式进行更改。根据字典的相等比较器,Dictionary<TKey, TValue>
中的每个键都必须是唯一的。键不能是Dictionary<TKey, TValue>
,但值可以是,如果值类型null
是引用类型。TValue
(来自MSDN)
与所有答案相反,随着 Record 类的引入,绝对可以为字典创建可变键。记录类需要使用 HashCode 结构实现 IEqualityComparer。 我可以举个例子