如果没有对引用类型执行任何特殊操作,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);
相关问题:
答案 0 :(得分:13)
是的,为此决定正确的规则是棘手的。这里没有单一的“正确”答案,而且在很大程度上取决于背景和偏好。就个人而言,我很少考虑这个问题,只是在大多数常规POCO课程中默认引用相等:
Person
之类的字典密钥/的情况最少
int Id
作为字典中的键(等等)无论如何 x==y
给出的结果相同,无论x
/ y
是Person
还是object
,还是T
通用方法Equals
和GetHashCode
兼容,大多数事情都会解决,而一个简单的方法就是不要覆盖它们 但请注意,我总是会建议相反的值类型,即明确覆盖Equals
/ GetHashCode
;但是,写一个struct
真的 不常见
答案 1 :(得分:6)
您可以提供多个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;
}
}
另请参阅:What is the best algorithm for an overridden 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;
答案 2 :(得分:1)
从根本上说,如果类类型字段(或变量,数组插槽等)X
和Y
各自包含对类对象的引用,则有两个逻辑问题{{1}可以回答:
请注意,如果(Object)X.Equals(Y)
和X
引用不同类型的对象,则这两个函数都不能合法地返回true,除非两个类都知道不存在任何存储位置,这些存储位置不能同时引用一个引用另一个[例如因为这两种类型都是从公共基础派生的私有类,并且都没有存储在任何存储位置(Y
除外),其类型不能同时包含对这两者的引用。
默认this
方法回答第一个问题; Object.Equals
回答了第二个问题。第一个问题通常是询问可观察状态可能发生变异的对象实例的问题;第二个适用于询问对象实例,即使其类型允许,其可观察状态也不会被突变。如果ValueType.Equals
和X
各自拥有对不同Y
的引用,并且两个数组在其第一个元素中保持23,则第一个相等关系应将它们定义为不同[复制{{1}如果int[1]
被修改,则X
会改变Y
的行为,但是第二个应该将它们视为等效的(交换对X[0]
和{{}的目标的所有引用{1}}不会影响任何事情)。请注意,如果数组保持不同的值,则第二个测试应该将数组视为不同,因为交换对象意味着Y[0]
现在将报告X
用于报告的值。
有一个非常强大的约定,即可变类型(Y
及其后代除外)应覆盖X[0]
以实现第一种类型的等价关系;因为Y[0]
或其后代不可能实现第一个关系,所以它们通常实现第二个关系。不幸的是,对于第一种关系覆盖System.ValueType
的对象,没有标准约定应该公开一个测试第二种关系的方法,即使可以定义一个允许任意两个任意对象之间进行比较的等价关系。类型。第二种关系在标准模式中很有用,其中不可变类Object.Equals
拥有对可变类型System.ValueType
的私有引用,但不会将该对象暴露给任何实际可能使其变异的代码[使实例不可变的]。在这种情况下,类Object.Equals()
无法知道实例永远不会被写入,但是有一个标准方法有助于Imm
的两个实例可以询问{{如果引用的持有者从未改变它们,它们所持有的引用是否等同于。请注意,上面定义的等价关系不会引用变异,也不会引用Mut
必须使用的任何特定方法来确保实例不会被突变,但其含义在任何情况下都是明确定义的。保存对Mut
的引用的对象应该知道该引用是否封装了标识,可变状态或不可变状态,因此应该能够适当地实现它自己的相等关系。