[英]Should an override of Equals on a reference type always mean value equality?
Without doing anything special for a reference type, Equals()
would mean reference equality (ie same object). 如果
Equals()
引用类型做任何特殊的处理, Equals()
将意味着引用相等(即同一对象)。 If I choose to override Equals()
for a reference type, should it always mean that the values of the two objects are equivalent? 如果选择为引用类型重写
Equals()
,是否总是意味着两个对象的值相等?
Consider this mutable Person
class: 考虑以下可变的
Person
类:
class Person
{
readonly int Id;
string FirstName { get; set; }
string LastName { get; set; }
string Address { get; set; }
// ...
}
Two objects that represent the exact same person will always have the same Id
, but the other fields might be different over time (ie before/after an address change). 代表完全相同的人的两个对象将始终具有相同的
Id
,但是其他字段可能随时间而不同(即,地址更改之前/之后)。
For this object Equals could be defined to mean different things: 对于此对象,可以将Equals定义为不同的含义:
Ids
are equal (two objects representing the same person but with different addresses would return true) Ids
相等(两个代表同一个人但地址不同的对象将返回true) Question: Which (if any) of these is preferable for this class? 问题:本课程中哪一个(如果有)更可取? (Or perhaps the question should be, "how would most clients of this class expect Equals() to behave?")
(或者也许应该是这样的问题,“大多数此类客户希望Equals()表现如何?”)
Notes: 笔记:
Hashset
or Dictionary
Hashset
或Dictionary
使用此类变得更加困难 Using Identity Equality makes the relationship between Equals and the =
operator strange (ie after a check of two Person objects (p1 and p2) returns true for Equals()
, you might still want to update your reference to point to the "newer" Person object since it is not value equivalent). 使用Identity Equality使Equals和
=
运算符之间的关系变得奇怪(即,在检查两个Person对象(p1和p2)对Equals()
返回true之后,您可能仍想更新引用以指向“较新的” Person对象,因为它不是等效的值)。 For example, the following code reads strange--seems like it does nothing, but it is actually removing p1 and adding p2: 例如,以下代码读来很奇怪-似乎它什么也没做,但实际上是在删除p1并添加p2:
HashSet<Person> people = new HashSet<Person>(); people.Add(p1); // ... p2 is an new object that has the same Id as p1 but different Address people.Remove(p2); people.Add(p2);
Related Questions: 相关问题:
Yes, deciding the right rules for this is tricky. 是的,为此确定正确的规则很棘手。 There is no single "right" answer here, and it will depend a lot on both context and preference Personally, I rarely bother thinking about it much, just defaulting to reference equality on most regular POCO classes:
这里没有单一的“正确”答案,这将在很大程度上取决于上下文和首选项。就我个人而言,我很少考虑这个问题,只是默认在大多数常规POCO类上引用相等性:
Person
as a dictionary-key / in a hash-set is minimal Person
作为字典键/之类的情况的次数很少
int Id
as the key in a dictionary (etc) anyway int Id
作为字典(等)中的键 x==y
gives the same result whether x
/ y
are Person
or object
, or indeed T
in a generic method x==y
给出相同的结果,无论x
/ y
是Person
还是object
,或者实际上是T
在通用方法中 Equals
and GetHashCode
are compatible, most things will just about work out, and one easy way to do that is to not override them Equals
和GetHashCode
兼容,大多数事情就可以解决了,一种简单的方法是不覆盖它们 Note, however, that I would always advise the opposite for value-types, ie explicitly override Equals
/ GetHashCode
; 但是请注意,对于值类型,我总是建议相反的做法,即显式重写
Equals
/ GetHashCode
; but then, writing a struct
is really uncommon 但是,然后写一个
struct
真的很罕见
You could provide multiple IEqualityComparer(T)
implementations and let the consumer decide. 您可以提供多个
IEqualityComparer(T)
实现,并让使用者自行决定。
Example: 例:
// Leave the class Equals as reference equality
class Person
{
readonly int Id;
string FirstName { get; set; }
string LastName { get; set; }
string Address { get; set; }
// ...
}
class PersonIdentityEqualityComparer : IEqualityComparer<Person>
{
public bool Equals(Person p1, Person p2)
{
if(p1 == null || p2 == null) return false;
return p1.Id == p2.Id;
}
public int GetHashCode(Person p)
{
return p.Id.GetHashCode();
}
}
class PersonValueEqualityComparer : IEqualityComparer<Person>
{
public bool Equals(Person p1, Person p2)
{
if(p1 == null || p2 == null) return false;
return p1.Id == p2.Id &&
p1.FirstName == p2.FirstName; // etc
}
public int GetHashCode(Person p)
{
int hash = 17;
hash = hash * 23 + p.Id.GetHashCode();
hash = hash * 23 + p.FirstName.GetHashCode();
// etc
return hash;
}
}
See also: What is the best algorithm for an overridden System.Object.GetHashCode? 另请参见: 重写System.Object.GetHashCode的最佳算法是什么?
Usage: 用法:
var personIdentityComparer = new PersonIdentityEqualityComparer();
var personValueComparer = new PersonValueEqualityComparer();
var joseph = new Person { Id = 1, FirstName = "Joseph" }
var persons = new List<Person>
{
new Person { Id = 1, FirstName = "Joe" },
new Person { Id = 2, FirstName = "Mary" },
joseph
};
var personsIdentity = new HashSet<Person>(persons, personIdentityComparer);
var personsValue = new HashSet<Person>(persons, personValueComparer);
var containsJoseph = personsIdentity.Contains(joseph);
Console.WriteLine(containsJoseph); // false;
containsJoseph = personsValue.Contains(joseph);
Console.WriteLine(containsJoseph); // true;
Fundamentally, if class-type fields (or variables, array slots, etc.) X
and Y
each hold a reference to a class object, there are two logical questions that (Object)X.Equals(Y)
can answer: 从根本上讲,如果类类型字段(或变量,数组插槽等)
X
和Y
各自持有对类对象的引用,则(Object)X.Equals(Y)
可以回答两个逻辑问题:
Note that if X
and Y
refer to objects of different types, neither function may legitimately return true unless both classes know that there cannot be any storage locations holding a reference to one which could not also hold a reference to the other [eg because both types are private classes derived from a common base, and neither is ever stored in any storage location (other than this
) whose type can't hold references to both]. 请注意,如果
X
和Y
引用不同类型的对象,则两个函数都不能合法地返回true,除非两个类都知道没有任何存储位置可以保存对一个对象的引用,也不能保存对另一个对象的引用(例如,因为两种类型是从公共基础派生的私有类,并且都不存储在类型不能同时引用两者的任何存储位置中(除了this
)。
The default Object.Equals
method answers the first question; 默认的
Object.Equals
方法回答第一个问题。 ValueType.Equals
answers the second. ValueType.Equals
回答第二个。 The first question is generally the appropriate one to ask of object instances whose observable state may be mutated; 通常,第一个问题是可观察状态可能发生突变的对象实例的合适问题。 the second is appropriate to ask of object instances whose observable state will not be mutated even if their types would allow it .
第二种方法适用于询问那些对象实例 , 即使其类型允许,其可观察状态也不会发生变化。 If
X
and Y
each hold a reference to a distinct int[1]
, and both arrays hold 23 in their first element, the first equality relation should define them as distinct [copying X
to Y
would alter the behavior of X[0]
if Y[0]
were modified], but the second should regard them as equivalent (swapping all references to the targets of X
and Y
wouldn't affect anything). 如果
X
和Y
各自持有对不同int[1]
的引用,并且两个数组在其第一个元素中均持有23,则第一个等式关系应将它们定义为不同[将X
复制到Y
将改变X[0]
的行为,如果Y[0]
已修改],但是第二个应该将它们视为等效(交换对X
和Y
目标的所有引用不会有任何影响)。 Note that if the arrays held different values, the second test should regard the arrays as distinct, since swapping the objects would mean X[0]
would now report the value that Y[0]
used to report). 请注意,如果数组持有不同的值,则第二项测试应将数组视为不同的,因为交换对象将意味着
X[0]
现在将报告Y[0]
用于报告的值)。
There's a pretty strong convention that mutable types (other than System.ValueType
and its descendants) should override Object.Equals
to implement the first type of equivalence relation; 有一个很强的约定,即可变类型(
System.ValueType
及其子代除外)应覆盖Object.Equals
。等于实现第一种等效关系; since it's impossible for System.ValueType
or its descendants to implement the first relation, they generally implement the second. 由于
System.ValueType
或其子代无法实现第一个关系,因此他们通常实现第二个关系。 Unfortunately, there's no standard convention by which objects which override Object.Equals()
for the first kind of relation should expose a method which tests for the second, even though an equivalence relation could be defined which allowed comparison between any two objects of any arbitrary type. 不幸的是,没有标准约定,即使第一个关系的对象都重写
Object.Equals()
,该对象也应公开测试第二个关系的方法,即使可以定义一个等价关系,以允许在任意两个对象之间进行比较类型。 The second relation would be useful in the standard pattern wherein an immutable class Imm
holds a private reference to a mutable type Mut
but doesn't expose that object to any code that could actually mutate it [making the instance immutable]. 第二种关系在标准模式中很有用,在该模式中,不可变类
Imm
持有对可变类型Mut
的私有引用,但不会将该对象暴露给可能对其进行实际修改的任何代码(使实例不可变)。 In such a case, there's no way for class Mut
to know that an instance will never be written, but it would be helpful to have a standard means by which two instances of Imm
could ask the Mut
s to which they hold references whether they would be equivalent if the holders of the references never mutated them . 在这种情况下,
Mut
类无法知道永远不会写入实例,但是采用一种标准的方法将有助于Imm
两个实例向其持有引用的Mut
询问是否愿意如果参考文献的持有者从未对其进行突变,则等同。 Note that the equivalence relation defined above makes no reference to mutation, nor to any particular means which Imm
must use to ensure that an instance won't be mutated, but its meaning is well-defined in any case. 请注意,上面定义的等价关系既没有引用突变,也没有引用
Imm
为确保实例不会突变而必须使用的任何特定方法,但是在任何情况下其含义都是明确定义的。 The object which holds a reference to Mut
should know whether that reference encapsulates identity, mutable state, or immutable state, and should thus be able to implement its own equality relation suitably. 持有对
Mut
的引用的对象应该知道该引用是封装身份,可变状态还是不可变状态,因此应该能够适当地实现其自身的相等关系。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.