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 是基于实例的,我认为这可以为开发人员节省大量时间。 具有值相等语义的可变类类型似乎是个坏主意。虽然有一些例外(例如double
、Decimal
、List<T>.Enumerator
等),但.net 中的大多数类型都实现了Equals(Object)
以表示等价;由于不同的可变类类型项永远不会等价,因此它们永远不应将自己报告为Equals
(List<T>.Enumerator
的行为源于装箱和未装箱的值类型具有不同的语义,但需要共享相同的 @987654328 @方法)。【参考方案3】:
它并没有避免这种情况。由调用代码来强制执行此操作:
只要将对象用作
Dictionary<TKey, TValue>
中的键,它就不能以任何影响其哈希值的方式发生变化。根据字典的相等比较器,Dictionary<TKey, TValue>
中的每个键都必须是唯一的。键不能是null
,但值可以是,如果值类型TValue
是引用类型。
(来自MSDN)
【讨论】:
【参考方案4】:Dictionary<TKey,TValue>
类型不会尝试防止用户修改所使用的密钥。完全由开发人员负责不改变密钥。
如果您仔细考虑一下,这确实是唯一合理的方法
Dictionary<TKey,TValue>
可以拿。考虑在对象上执行类似成员克隆的操作的含义。为了彻底,您需要进行深度克隆,因为键中引用的对象也可能发生变异,从而影响哈希码。因此,现在表中使用的每个键都克隆了完整的对象图,以防止突变。这将是错误的,并且可能是一项非常昂贵的操作。
【讨论】:
Python 虽然采用了另一种方式,并且不允许可变数据作为缓存键。 udacity.com/view#Course/cs212/CourseRev/apr2012/Unit/207010/…【参考方案5】:Dictionary<>
类不会保护自己免受可变键对象的更改。由您决定您用作键的类是否是可变的,并尽可能避免它。
【讨论】:
以上是关于C# Dictionary<> 和可变键的主要内容,如果未能解决你的问题,请参考以下文章
C# 将 List<string> 转换为 Dictionary<string, string>