[英]Should an override of Equals on a reference type always mean value equality?
如果Equals()
引用类型做任何特殊的处理, Equals()
将意味着引用相等(即同一对象)。 如果选择为引用类型重写Equals()
,是否总是意味着两个对象的值相等?
考虑以下可变的Person
类:
class Person
{
readonly int Id;
string FirstName { get; set; }
string LastName { get; set; }
string Address { get; set; }
// ...
}
代表完全相同的人的两个对象将始终具有相同的Id
,但是其他字段可能随时间而不同(即,地址更改之前/之后)。
对于此对象,可以将Equals定义为不同的含义:
Ids
相等(两个代表同一个人但地址不同的对象将返回true) 问题:本课程中哪一个(如果有)更可取? (或者也许应该是这样的问题,“大多数此类客户希望Equals()表现如何?”)
笔记:
Hashset
或Dictionary
使用此类变得更加困难 使用Identity Equality使Equals和=
运算符之间的关系变得奇怪(即,在检查两个Person对象(p1和p2)对Equals()
返回true之后,您可能仍想更新引用以指向“较新的” Person对象,因为它不是等效的值)。 例如,以下代码读来很奇怪-似乎它什么也没做,但实际上是在删除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);
相关问题:
是的,为此确定正确的规则很棘手。 这里没有单一的“正确”答案,这将在很大程度上取决于上下文和首选项。就我个人而言,我很少考虑这个问题,只是默认在大多数常规POCO类上引用相等性:
Person
作为字典键/之类的情况的次数很少
int Id
作为字典(等)中的键 x==y
给出相同的结果,无论x
/ y
是Person
还是object
,或者实际上是T
在通用方法中 Equals
和GetHashCode
兼容,大多数事情就可以解决了,一种简单的方法是不覆盖它们 但是请注意,对于值类型,我总是建议相反的做法,即显式重写Equals
/ GetHashCode
; 但是,然后写一个struct
真的很罕见
您可以提供多个IEqualityComparer(T)
实现,并让使用者自行决定。
例:
// 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;
}
}
另请参见: 重写System.Object.GetHashCode的最佳算法是什么?
用法:
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;
从根本上讲,如果类类型字段(或变量,数组插槽等) X
和Y
各自持有对类对象的引用,则(Object)X.Equals(Y)
可以回答两个逻辑问题:
请注意,如果X
和Y
引用不同类型的对象,则两个函数都不能合法地返回true,除非两个类都知道没有任何存储位置可以保存对一个对象的引用,也不能保存对另一个对象的引用(例如,因为两种类型是从公共基础派生的私有类,并且都不存储在类型不能同时引用两者的任何存储位置中(除了this
)。
默认的Object.Equals
方法回答第一个问题。 ValueType.Equals
回答第二个。 通常,第一个问题是可观察状态可能发生突变的对象实例的合适问题。 第二种方法适用于询问那些对象实例 , 即使其类型允许,其可观察状态也不会发生变化。 如果X
和Y
各自持有对不同int[1]
的引用,并且两个数组在其第一个元素中均持有23,则第一个等式关系应将它们定义为不同[将X
复制到Y
将改变X[0]
的行为,如果Y[0]
已修改],但是第二个应该将它们视为等效(交换对X
和Y
目标的所有引用不会有任何影响)。 请注意,如果数组持有不同的值,则第二项测试应将数组视为不同的,因为交换对象将意味着X[0]
现在将报告Y[0]
用于报告的值)。
有一个很强的约定,即可变类型( System.ValueType
及其子代除外)应覆盖Object.Equals
。等于实现第一种等效关系; 由于System.ValueType
或其子代无法实现第一个关系,因此他们通常实现第二个关系。 不幸的是,没有标准约定,即使第一个关系的对象都重写Object.Equals()
,该对象也应公开测试第二个关系的方法,即使可以定义一个等价关系,以允许在任意两个对象之间进行比较类型。 第二种关系在标准模式中很有用,在该模式中,不可变类Imm
持有对可变类型Mut
的私有引用,但不会将该对象暴露给可能对其进行实际修改的任何代码(使实例不可变)。 在这种情况下, Mut
类无法知道永远不会写入实例,但是采用一种标准的方法将有助于Imm
两个实例向其持有引用的Mut
询问是否愿意如果参考文献的持有者从未对其进行突变,则等同。 请注意,上面定义的等价关系既没有引用突变,也没有引用Imm
为确保实例不会突变而必须使用的任何特定方法,但是在任何情况下其含义都是明确定义的。 持有对Mut
的引用的对象应该知道该引用是封装身份,可变状态还是不可变状态,因此应该能够适当地实现其自身的相等关系。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.