C# 使用类与结构作为字典键

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16472159/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 01:05:10  来源:igfitidea点击:

Using a class versus struct as a dictionary key

c#classdictionarystructequality

提问by Kyle Baran

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.

它说密钥已经存在,正如预期的那样,但仅适用于结构。该类将其视为两个单独的条目。我认为这与作为参考与价值的数据有关,但我想更详细地解释原因。

采纳答案by smartcaveman

  1. new object() == new object()is false, because reference types have reference equality and the two instances are not the same reference

  2. new int() == new int()is true, because value types have value equality and the value of two default integers are the same value. 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.

  1. new object() == new object()false,因为引用类型具有引用相等性,并且两个实例不是同一个引用

  2. new int() == new int()true,因为值类型具有值相等性并且两个默认整数的值是相同的值。请注意,如果您的结构中具有递增的引用类型或默认值,则结构的默认值也可能不相等。

You can override the Equalsand GetHashCodemethods 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;which will add new values or update old ones with the same key.

此外,如果您想要一种安全的方式来设置字典值,无论如何,您都可以dictionary[key] = value;使用相同的键添加新值或更新旧值。

Update

更新

@280Z28posted a commentthat pointed out how this answer could be misleading, which I recognize and want to address. It's important to know that:

@280Z28发表了一条评论,指出这个答案可能具有误导性,我承认并希望解决这一问题。重要的是要知道:

  1. By default, reference types' Equals(object obj)method and ==operator call object.ReferenceEquals(this, obj)under the hood.

  2. The operators and instance methods need to be overridden eventually to propagate the behavior. (e.g. changing the Equalsimplementation will not affect the ==implementation, unless a nested call is added explicitly).

  3. All the default .NET generic collections use an IEqualityComparer<T>implementation to determine equality (not an instance method). The IEqualityComparer<T>may (and often does) call the instance method in its implementation, but this is not something you can count on. There are two possible sources for the IEqualityComparer<T>implementation that is used:

    1. You can provide it explicitly in the constructor.

    2. It will be retrieved automatically from EqualityComparer<T>.Default(by default). If you want to configure the default IEqualityComparer<T>globally that is accessed by EqualityComparer<T>.Default, you can use Undefault(on GitHub).

  1. 默认情况下,引用类型的Equals(object obj)方法和==操作符object.ReferenceEquals(this, obj)在幕后调用。

  2. 最终需要覆盖运算符和实例方法以传播行为。(例如,更改Equals实现不会影响==实现,除非显式添加了嵌套调用)。

  3. 所有默认的 .NET 泛型集合都使用IEqualityComparer<T>实现来确定相等性(而不是实例方法)。该IEqualityComparer<T>可以(而且经常)调用实例方法在其实施,但是这是不是你可以指望。IEqualityComparer<T>使用的实现有两种可能的来源:

    1. 您可以在构造函数中显式提供它。

    2. 它将从EqualityComparer<T>.Default(默认情况下)自动检索。如果要配置IEqualityComparer<T>访问的全局默认值EqualityComparer<T>.Default,可以使用Undefault(在 GitHub 上)。

回答by Mathieu Guindon

The reference typekey (class) points to a distinct reference; the value typekey (struct) points to identical values. I would think that's why you get the exception.

引用类型密钥(类)指向一个不同的参考; 的值类型密钥(结构)指向相同的值。我认为这就是为什么你会得到例外。

回答by Jens Kloster

Beause a structis not refered like a class.

因为 astruct不像 a 那样被引用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

//会打印真

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

// 将打印错误!(假设你没有实现一些比较功能)因为引用不一样

回答by Sam Harwell

Dictionary<TKey, TValue>uses an IEqualityComparer<TKey>for comparing the keys. If you do not explicitly specify the comparer when you construct the dictionary, it will use EqualityComparer<TKey>.Default.

Dictionary<TKey, TValue>使用IEqualityComparer<TKey>来比较键。如果在构造字典时没有明确指定比较器,它将使用EqualityComparer<TKey>.Default.

Since neither MyClassnor MyStructimplement IEquatable<T>, the default equality comparer will call Object.Equalsand Object.GetHashCodefor comparing instances. MyClassis derived from Object, so the implementation will use reference equality for comparison. MyStructon the other hand is derived from System.ValueType(the base class of all structs), so it will use ValueType.Equalsfor comparing the instances. The documentation for this method states the following:

由于既不实现MyClass也不MyStruct实现IEquatable<T>,默认的相等比较器将调用Object.EqualsObject.GetHashCode来比较实例。MyClass派生自Object,因此实现将使用引用相等进行比较。MyStruct另一方面是从System.ValueType(所有结构的基类)派生的,因此它将ValueType.Equals用于比较实例。此方法的文档说明如下:

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.

If none of the fields of the current instance and objare reference types, the Equalsmethod performs a byte-by-byte comparison of the two objects in memory. Otherwise, it uses reflection to compare the corresponding fields of objand this instance.

ValueType.Equals(Object)方法Object.Equals(Object)为 .NET Framework 中的所有值类型覆盖并提供值相等的默认实现。

如果当前实例的字段都不obj是引用类型,则该Equals方法对内存中的两个对象进行逐字节比较。否则,它使用反射来比较obj和这个实例的对应字段。

The exception occurs because IDictionary<TKey, TValue>.Addthrows an ArgumentExceptionif "An element with the same key already exists in the [dictionary]." When using structs, the byte-by-byte comparison done by ValueType.Equalsresults in both calls attempting to add the same key.

发生异常是因为IDictionary<TKey, TValue>.Add抛出一个ArgumentExceptionif “[dictionary] 中已经存在具有相同键的元素。” 使用结构体时,逐字节比较会ValueType.Equals导致两个调用都尝试添加相同的键。

回答by supercat

There are generally three good types of dictionary keys: the identitiesof 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 identityof the object, rather than its contents.

字典键通常分为三种类型:可变类对象的身份、不可变类对象的值或结构的值。请注意,具有公开公共字段的结构与不公开公共字段的结构一样适合用作字典键,因为存储在字典中的结构副本将更改的唯一方式是读取、修改和写入结构背部。相比之下,具有公开可变属性的类通常会生成糟糕的字典键,除非希望以对象的身份而不是其内容为键。

In order for a type to be used as a dictionary key, either its Equalsand GetHashCodemethods must have the desired semantics, or else the constructor of the Dictionarymust be given an IEqualityComparer<T>which implements the desired semantics. The default Equalsand GetHashCodemethod for classes will key on object identity (useful if one wishes to key upon the identities of mutable objects; not so useful otherwise). The default Equalsand GetHashCodemethods for value types will generally key upon the Equalsand GetHashCodemethods of their members, but with a couple of wrinkles:

为了将类型用作字典键,它的EqualsGetHashCode方法必须具有所需的语义,否则Dictionary必须为的构造函数提供IEqualityComparer<T>实现所需语义的an 。类的默认值EqualsGetHashCode方法将锁定对象身份(如果希望锁定可变对象的身份,则很有用;否则就没那么有用了)。值类型的默认值EqualsGetHashCode方法通常取决于其成员的EqualsGetHashCode方法,但有一些皱纹:

  • Code using the default methods on structures will often run muchslower (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. 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.

  • 在结构上使用默认方法的代码通常比使用自定义编写方法的代码运行慢得多(有时是一个数量级)。

  • 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. 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.

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".

如果性能远非关键,则可以定义一个简单的结构 [只需声明适当的公共字段] 并将其放入 Dictionary 中并使其表现为基于值的键。它不会非常有效,但它会起作用。字典通常会更有效地处理不可变类对象,但定义和使用不可变类对象有时比定义和使用“普通的旧数据结构”更重要。