简体   繁体   English

使用类与struct作为字典键

[英]Using a class versus struct as a dictionary key

Suppose I had the following class and structure definition, and used them each as a key in a dictionary object: 假设我有以下类和结构定义,并将它们作为字典对象中的键使用:

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>();

Why is it that this works: 为什么这有效:

MyClass classA = new MyClass();
MyClass classB = new MyClass();
this.ClassDictionary.Add(classA, "Test");
this.ClassDictionary.Add(classB, "Test");

But this crashes on runtime: 但是这会在运行时崩溃:

MyStruct structA = new MyStruct();
MyStruct structB = new MyStruct();
this.StructDictionary.Add(structA, "Test");
this.StructDictionary.Add(structB, "Test");

It says the key already exists, as expected, but only for the struct. 它表示密钥已经存在,正如预期的那样,但仅适用于结构。 The class treats it as two separate entries. 该类将其视为两个单独的条目。 I think it has something to do with the data being held as a reference versus value, but I would like a more detailed explanation as to why. 我认为这与作为参考与价值的数据有关,但我想更详细地解释原因。

Dictionary<TKey, TValue> uses an IEqualityComparer<TKey> for comparing the keys. Dictionary<TKey, TValue>使用IEqualityComparer<TKey>来比较密钥。 If you do not explicitly specify the comparer when you construct the dictionary, it will use EqualityComparer<TKey>.Default . 如果在构造字典时没有显式指定比较器,则它将使用EqualityComparer<TKey>.Default

Since neither MyClass nor MyStruct implement IEquatable<T> , the default equality comparer will call Object.Equals and Object.GetHashCode for comparing instances. 由于MyClassMyStruct实现IEquatable<T> ,因此默认的相等比较器将调用Object.EqualsObject.GetHashCode来比较实例。 MyClass is derived from Object , so the implementation will use reference equality for comparison. MyClass派生自Object ,因此实现将使用引用相等进行比较。 MyStruct on the other hand is derived from System.ValueType (the base class of all structs), so it will use ValueType.Equals for comparing the instances. 另一方面, MyStruct派生自System.ValueType (所有结构的基类),因此它将使用ValueType.Equals来比较实例。 The documentation for this method states the following: 此方法的文档说明如下:

The ValueType.Equals(Object) method overrides Object.Equals(Object) and provides the default implementation of value equality for all value types in the .NET Framework. ValueType.Equals(Object)方法重写Object.Equals(Object)并为.NET Framework中的所有值类型提供值相等的默认实现。

If none of the fields of the current instance and obj are reference types, the Equals method performs a byte-by-byte comparison of the two objects in memory. 如果当前实例和obj任何字段都不是引用类型,则Equals方法Equals内存中的两个对象执行逐字节比较。 Otherwise, it uses reflection to compare the corresponding fields of obj and this instance. 否则,它使用反射来比较obj和此实例的相应字段。

The exception occurs because IDictionary<TKey, TValue>.Add throws an ArgumentException if "An element with the same key already exists in the [dictionary]." 发生异常是因为IDictionary<TKey, TValue>.Add会抛出ArgumentException如果“[dictionary]中已经存在具有相同键的元素”。 When using structs, the byte-by-byte comparison done by ValueType.Equals results in both calls attempting to add the same key. 使用结构时, ValueType.Equals完成的逐字节比较会导致两个调用都尝试添加相同的键。

  1. new object() == new object() is false , because reference types have reference equality and the two instances are not the same reference new object() == new object()false ,因为引用类型具有引用相等性,并且这两个实例不是同一引用

  2. new int() == new int() is true , because value types have value equality and the value of two default integers are the same value. new int() == new int()true ,因为值类型具有值相等,并且两个默认整数的值是相同的值。 Note, that if you have reference types or default values that are incremental in your struct, the defaults may not compare equal for structs either. 请注意,如果您的结构中具有增量的引用类型或默认值,则默认值可能不会比较结构的相等。

You can override the Equals and GetHashCode methods and the equality operators of both structs and classes if you don't like the default equality behavior. 如果您不喜欢默认的相等行为,则可以覆盖EqualsGetHashCode方法以及结构和类的相等运算符。

Also, If you want a safe way to set the dictionary value, regardless, you can do dictionary[key] = value; 另外,如果你想要一种安全的方式设置字典值,无论如何,你可以做dictionary[key] = value; which will add new values or update old ones with the same key. 这将添加新值或使用相同的键更新旧值。

Update 更新

@280Z28 posted a comment that pointed out how this answer could be misleading, which I recognize and want to address. @ 280Z28发表了一条评论 ,指出这个答案可能会产生误导,我承认并希望解决这个问题。 It's important to know that: 重要的是要知道:

  1. By default, reference types' Equals(object obj) method and == operator call object.ReferenceEquals(this, obj) under the hood. 默认情况下,引用类型' Equals(object obj)方法和==操作符调用object.ReferenceEquals(this, obj)

  2. The operators and instance methods need to be overridden eventually to propagate the behavior. 最终需要覆盖运算符和实例方法以传播行为。 (eg changing the Equals implementation will not affect the == implementation, unless a nested call is added explicitly). (例如,更改Equals实现不会影响==实现,除非显式添加嵌套调用)。

  3. All the default .NET generic collections use an IEqualityComparer<T> implementation to determine equality (not an instance method). 所有默认的.NET泛型集合都使用IEqualityComparer<T>实现来确定相等性(不是实例方法)。 The IEqualityComparer<T> may (and often does) call the instance method in its implementation, but this is not something you can count on. IEqualityComparer<T>可以(并且经常)在其实现中调用实例方法,但这不是您可以依赖的东西。 There are two possible sources for the IEqualityComparer<T> implementation that is used: 使用的IEqualityComparer<T>实现有两种可能的来源:

    1. You can provide it explicitly in the constructor. 您可以在构造函数中显式提供它。

    2. It will be retrieved automatically from EqualityComparer<T>.Default (by default). 它将从EqualityComparer<T>.Default (默认情况下)自动检索。 If you want to configure the default IEqualityComparer<T> globally that is accessed by EqualityComparer<T>.Default , you can use Undefault (on GitHub). 如果要全局配置EqualityComparer<T>.Default访问的默认IEqualityComparer<T> ,则可以使用Undefault (在GitHub上)。

There are generally three good types of dictionary keys: the identities of mutable class objects, the values of immutable class objects, or the values of structures. 通常有三种类型的字典键:可变类对象的标识 ,不可变类对象的值或结构的值。 Note that structures with exposed public fields are just as suitable for use as dictionary keys as those which do not, since the only way the copy of the structure stored within the dictionary will change will be if the structure is read out, modified, and written back. 请注意,具有公开公共字段的结构与不使用公共字段的结构一样适合用作字典键,因为如果结构被读出,修改和写入,则存储在字典中的结构的副本将改变的唯一方式是背部。 By contrast, classes with exposed mutable properties generally make lousy dictionary keys except in the case where one wishes to key upon the identity of the object, rather than its contents. 相比之下,具有暴露的可变属性的类通常会产生糟糕的字典键,除非在希望键入对象的标识而不是其内容的情况下。

In order for a type to be used as a dictionary key, either its Equals and GetHashCode methods must have the desired semantics, or else the constructor of the Dictionary must be given an IEqualityComparer<T> which implements the desired semantics. 为了将类型用作字典键,其EqualsGetHashCode方法必须具有所需的语义,否则必须为Dictionary的构造函数提供实现所需语义的IEqualityComparer<T> The default Equals and GetHashCode method for classes will key on object identity (useful if one wishes to key upon the identities of mutable objects; not so useful otherwise). 类的默认EqualsGetHashCode方法将键入对象标识(如果希望键入可变对象的标识,则非常有用;否则不会那么有用)。 The default Equals and GetHashCode methods for value types will generally key upon the Equals and GetHashCode methods of their members, but with a couple of wrinkles: 值类型的默认EqualsGetHashCode方法通常会关注其成员的EqualsGetHashCode方法,但有一些皱纹:

  • Code using the default methods on structures will often run much slower (sometimes an order of magnitude) than code which uses custom-written methods. 使用结构上的默认方法的代码通常比使用自定义编写方法的代码运行慢得多(有时是一个数量级)。

  • Structures which contain only primitive types will perform floating-point comparisons differently from those which include other types as well. 仅包含基本类型的结构将执行与包含其他类型的结构不同的浮点比较。 For example, the values posZero=(1.0/(1.0/0.0)) and negZero=(-1.0/(1.0/0.0)) will both compare equal, but if stored in a struct that contains only primitives they will compare unequal. 例如,值posZero =(1.0 /(1.0 / 0.0))和negZero =( - 1.0 /(1.0 / 0.0))将比较相等,但如果存储在仅包含基元的结构中,它们将比较不相等。 Note that even thought he values compare equal, they are semantically not the same, since computing 1.0/posZero will yield positive infinity, and 1.0/negZero will yield negative infinity. 请注意,即使他认为他的值相等,它们在语义上也不相同,因为计算1.0 / posZero将产生正无穷大,而1.0 / negZero将产生负无穷大。

If performance is nowhere near critical, one may define a simple struct [simply declare the appropriate public fields] and throw it into a Dictionary and have it behave as a value-based key. 如果性能不是至关重要的,那么可以定义一个简单的结构[简单地声明适当的公共字段]并将其抛入字典并使其表现为基于值的键。 It won't be terribly efficient, but it will work. 它不会非常有效,但它会起作用。 Dictionaries will generally handle immutable class objects somewhat more efficiently, but defining and using immutable class objects can sometimes be more work than defining and using "plain old data structures". 字典通常会更有效地处理不可变类对象,但是定义和使用不可变类对象有时比定义和使用“普通旧数据结构”更有用。

Beause a struct is not refered like a class . 因为struct不像class那样被引用。

A struct creates a copy of itself instead og parsing a reference like a class do. 结构创建自己的副本,而不是像类一样解析引用。

Therefor if you try this: 因此,如果您尝试这样做:

var a =  new MyStruct(){Prop = "Test"};
var b =  new MyStruct(){Prop = "Test"};

Console.WriteLine(a.Equals(b));

//will print true //将打印为true

If you do the same with a class: 如果你对一个类做同样的事情:

var a =  new MyClass(){Prop = "Test"};
var b =  new MyClass(){Prop = "Test"};

Console.WriteLine(a.Equals(b));

// will print false! //将打印错误! (assuming you have not implemented some compare function) beacuse the reference is not the same (假设你没有实现一些比较功能)因为参考不一样

The reference type key (class) points to a distinct reference; 引用类型键(类)指向不同的引用; the value type key (struct) points to identical values. 值类型键(struct)指向相同的值。 I would think that's why you get the exception. 我认为这就是你获得例外的原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM