Kyl*_*ran 29 c# struct dictionary equality class
假设我有以下类和结构定义,并将它们作为字典对象中的键使用:
public class MyClass { }
public struct MyStruct { }
public Dictionary<MyClass, string> ClassDictionary;
public Dictionary<MyStruct, string> StructDictionary;
ClassDictionary = new Dictionary<MyClass, string>();
StructDictionary = new Dictionary<MyStruct, string>();
Run Code Online (Sandbox Code Playgroud)
为什么这有效:
MyClass classA = new MyClass();
MyClass classB = new MyClass();
this.ClassDictionary.Add(classA, "Test");
this.ClassDictionary.Add(classB, "Test");
Run Code Online (Sandbox Code Playgroud)
但是这会在运行时崩溃:
MyStruct structA = new MyStruct();
MyStruct structB = new MyStruct();
this.StructDictionary.Add(structA, "Test");
this.StructDictionary.Add(structB, "Test");
Run Code Online (Sandbox Code Playgroud)
它表示密钥已经存在,正如预期的那样,但仅适用于结构.该类将其视为两个单独的条目.我认为这与作为参考与价值的数据有关,但我想更详细地解释原因.
Sam*_*ell 58
Dictionary<TKey, TValue>采用了IEqualityComparer<TKey>比较的关键.如果在构造字典时没有显式指定比较器,它将使用EqualityComparer<TKey>.Default.
由于既没有MyClass也没有MyStruct实现IEquatable<T>,默认的相等比较器将调用Object.Equals和Object.GetHashCode比较实例.MyClass是派生自Object,因此实现将使用引用相等进行比较.MyStruct另一方面,它派生自System.ValueType(所有结构的基类),因此它将ValueType.Equals用于比较实例.此方法的文档说明如下:
该
ValueType.Equals(Object)方法覆盖Object.Equals(Object)并为.NET Framework中的所有值类型提供值相等的默认实现.如果当前实例的任何字段都不
obj是引用类型,则该Equals方法将对内存中的两个对象执行逐字节比较.否则,它使用反射来比较obj该实例的相应字段.
发生异常是因为IDictionary<TKey, TValue>.Add抛出一个ArgumentExceptionif"[dictionary]中已存在具有相同键的元素." 使用结构时,ValueType.Equals在两个调用中尝试添加相同键的结果进行逐字节比较.
sma*_*man 14
new object() == new object()是false,因为引用类型具有引用相等性,并且这两个实例不是相同的引用
new int() == new int()是真实的,因为价值类型具有价值平等的两个默认整数值是相同的值.请注意,如果您的结构中具有增量的引用类型或默认值,则默认值可能不会比较结构的相等.
如果您不喜欢默认的相等行为,则可以覆盖结构和类的Equals和GetHashCode方法以及相等运算符.
此外,如果您想要一种安全的方法来设置字典值,无论如何,您都可以执行dictionary[key] = value;添加新值或使用相同键更新旧值.
@ 280Z28发表了一条评论,指出这个答案可能会产生误导,我承认并希望解决这个问题.重要的是要知道:
默认情况下,引擎类型的Equals(object obj)方法和==操作员调用object.ReferenceEquals(this, obj).
最终需要覆盖运算符和实例方法以传播行为.(例如,更改Equals实现不会影响==实现,除非显式添加嵌套调用).
所有默认的.NET泛型集合都使用IEqualityComparer<T>实现来确定相等性(而不是实例方法).在IEqualityComparer<T>5月(而且经常)调用实例方法在其实施,但是这是不是你可以指望.IEqualityComparer<T>使用的实现有两种可能的来源:
您可以在构造函数中显式提供它.
它将自动从EqualityComparer<T>.Default(默认情况下)检索.如果要配置IEqualityComparer<T>全局访问的默认值EqualityComparer<T>.Default,可以使用Undefault(在GitHub上).
通常有三种类型的字典键:可变类对象的标识,不可变类对象的值或结构的值.请注意,具有公开公共字段的结构与不使用公共字段的结构一样适合用作字典键,因为如果结构被读出,修改和写入,则存储在字典中的结构的副本将改变的唯一方式是背部.相比之下,具有暴露的可变属性的类通常会产生糟糕的字典键,除非在希望键入对象的标识而不是其内容的情况下.
为了使一个类型用作字典键,它的Equals和GetHashCode方法必须具有所需的语义,否则Dictionary必须给出一个IEqualityComparer<T>实现所需语义的构造函数.类的默认Equals和GetHashCode方法将键入对象标识(如果希望键入可变对象的标识,则非常有用;否则不会那么有用).默认Equals和GetHashCode值类型的方法将在普遍键入Equals和GetHashCode其成员的方法,但有几个皱纹:
使用结构上的默认方法的代码通常比使用自定义编写方法的代码运行得慢得多(有时是一个数量级).
仅包含基本类型的结构将执行与包含其他类型的结构不同的浮点比较.例如,值= posZero(1.0 /(1.0/0.0))和negZero =( - 1.0 /(1.0/0.0))都比较相等,但如果存储在仅包含原语一个struct它们将比较不等.请注意,即使他认为他的值相等,它们在语义上也不相同,因为计算1.0/posZero将产生正无穷大,而1.0/negZero将产生负无穷大.
如果性能不是至关重要的,那么可以定义一个简单的结构[简单地声明适当的公共字段]并将其抛入字典并使其表现为基于值的键.它不会非常有效,但它会起作用.字典通常会更有效地处理不可变类对象,但是定义和使用不可变类对象有时比定义和使用"普通旧数据结构"更有用.