简体   繁体   English

定义operator ==但没有定义Equals()或GetHashCode()有什么问题?

[英]What's wrong with defining operator == but not defining Equals() or GetHashCode()?

For the code below 对于下面的代码

public struct Person
{
    public int ID;
    public static bool operator ==(Person a, Person b) { return  a.Equals(b); }
    public static bool operator !=(Person a, Person b) { return !a.Equals(b); }
}

Why does the compiler give me these warnings? 为什么编译器会给我这些警告?
What's wrong with not defining the methods below? 没有定义下面的方法有什么问题?

warning CS0660: 'Person' defines operator == or operator != but
    does not override Object.Equals(object o)

warning CS0661: 'Person' defines operator == or operator != but
    does not override Object.GetHashCode()

EDIT : This answer has been corrected, among other things to note that user-defined value types don't generate == , and to mention the performance issues with ValueType.Equals . 编辑 :此答案已得到纠正,除其他事项外,请注意用户定义的值类型不生成== ,并提及ValueType.Equals的性能问题。


In general, overridding one, but not all, is confusing. 一般来说,覆盖一个,但不是全部,是令人困惑的。 The user expects neither to be overridden, or both to be, with the same semantics. 用户希望既不会被覆盖,也不会被覆盖,具有相同的语义。

Microsoft's recommendations for this state (among other things): 微软对此州的建议 (以及其他事项):

  • Implement the GetHashCode method whenever you implement the Equals method. 实现Equals方法时实现GetHashCode方法。 This keeps Equals and GetHashCode synchronized. 这使Equals和GetHashCode保持同步。

  • Override the Equals method whenever you implement the equality operator (==), and make them do the same thing. 每当实现相等运算符(==)时重写Equals方法,并使它们执行相同的操作。

In your case, you have a valid reason to defer to Equals (the compiler doesn't automatically implement == ) and override only those two ( == / != ). 在您的情况下,您有正当理由推迟到Equals (编译器不会自动实现== )并仅覆盖那两个( == / != )。 However, there's still a performance issue, since ValueType.Equals uses reflection: 但是,由于ValueType.Equals使用反射,因此仍存在性能问题:

"Override the Equals method for a particular type to improve the performance of the method and more closely represent the concept of equality for the type." “覆盖特定类型的Equals方法以提高方法的性能,并更接近地表示类型的相等概念。”

Thus, it's still recommended to override all ( == / != / Equals ) in the end. 因此,仍然建议最后覆盖所有( == / != / Equals )。 Of course, performance may not matter for this trivial struct. 当然,对于这个简单的结构,性能可能无关紧要。

There is a general expectation within the Framework that certain operations should always produce the same result. 框架内普遍期望某些操作应始终产生相同的结果。 The reason is that certain operations (in particular, sorting and searching, which make up a large portion of any application) rely on these different operations producing meaningful and consistent results. 原因是某些操作(特别是排序和搜索,构成任何应用程序的很大一部分)依赖于这些不同的操作,从而产生有意义且一致的结果。 In this case, you are breaking a couple of those assumptions: 在这种情况下,您打破了以下几个假设:

  • If there is a valid operation == between a and b , it should produce the same result as a.Equals(b) 如果ab之间存在有效的操作== ,则应该产生与a.Equals(b)相同的结果
  • Similar, if there is a valid operation != between a and b , it should produce the same result as !a.Equals(b) 类似的,如果在ab之间存在有效的操作!= ,它应该产生与!a.Equals(b)相同的结果
  • If two objects a and b exist, for which a == b , then a and b should produce the same key when stored in a hash table. 如果存在两个对象ab ,其中a == b ,则ab在存储在哈希表中时应生成相同的密钥。

The first two, IMO, are obvious; IMO的前两个是显而易见的; if you are defining what it means for two objects to be equal, you should include all of the ways you can check for two objects to be equal. 如果要定义两个对象相等的含义,则应该包括检查两个对象是否相等的所有方法。 Note that the compiler doesn't (in general, cannot ) enforce that you actually follow those rules. 请注意,编译器(通常不能 )强制您实际遵循这些规则。 It's not going to perform complex code analysis of the body of your operators to see if they already mimic Equals because, in the worst case, that could be equivalent to solving the halting problem. 它不会对运算符的主体执行复杂的代码分析,看看它们是否已经模仿Equals因为在最坏的情况下,这可能等同于解决暂停问题。

What it can do, however, is check for cases where you most likely are breaking those rules, in particular, you provided custom comparison operators and did not provide a custom Equals method. 但它可以做的是检查你最有可能破坏这些规则的情况,特别是你提供了自定义比较运算符而没有提供自定义的Equals方法。 The assumption here is that you would not have bothered to provide operators if you did not want them to do something special, in which case, you should have provided custom behavior for all of the methods that need to be in sync. 这里的假设是,如果您不希望它们执行某些特殊操作,您就不会费心提供运算符,在这种情况下,您应该为所有需要同步的方法提供自定义行为。

If you did implement Equals to be something different from == the compiler would not complain; 如果你确实将Equals实现为与==不同的东西,那么编译器就不会抱怨; you would have hit the limit of how hard C# is willing to try to prevent you from doing something stupid. 你会达到C#愿意试图阻止你做一些愚蠢事情的极限。 It was willing to stop you from accidentally introducing subtle bugs in your code, but it's going to let you purposely do so if that's what you want. 它愿意阻止你不小心在你的代码中引入微妙的错误,但是如果这就是你想要的,它会让你故意这样做。

The third assumption has to do with the fact that many internal operations in the Framework use some variant of a hash table. 第三个假设与框架中的许多内部操作使用哈希表的某些变体这一事实有关。 If I have two objects that are, by my definition, "equal", then I should be able to do this: 如果我有两个对象,根据我的定义,“相等”,那么我应该能够这样做:

if (a == b)
{
    var tbl = new HashTable();
    tbl.Add(a, "Test");

    var s = tbl[b];
    Debug.Assert(s.Equals("Test"));
}

This is a basic property of hash tables that would cause very strange problems if it were suddenly not true. 这是哈希表的基本属性,如果它突然不正确会导致非常奇怪的问题。

My guess is your are getting these warning because compiler doesn't know that you use Equals in == method 我的猜测是你得到这些警告,因为编译器不知道你在==方法中使用Equals

Suppose you have this implementation 假设您有此实现

public struct  Person
{
    public int ID;
    public static bool operator ==(Person a, Person b) { return Math.Abs(a.ID - b.ID) <= 5; }
    public static bool operator !=(Person a, Person b) { return Math.Abs(a.ID - b.ID) > 5; }
}

Then 然后

 Person p1 = new Person() { ID = 1 };
 Person p2 = new Person() { ID = 4 };

 bool b1 = p1 == p2;
 bool b2 = p1.Equals(p2);

b1 would be true , but b2 false b1是真的 ,但是b2是假的

--EDIT-- - 编辑 -

Now suppose you want to do this 现在假设您想要这样做

Dictionary<Person, Person> dict = new Dictionary<Person, Person>();
dict.Add(p1, p1);
var x1 = dict[p2]; //Since p2 is supposed to be equal to p1 (according to `==`), this should return p1

But this would throw an exception something like KeyNotFound 但这会抛出类似KeyNotFound的异常

But if you add 但是,如果你添加

public override bool Equals(object obj)
{
    return Math.Abs(ID - ((Person)obj).ID) <= 5; 
}
public override int GetHashCode()
{
    return 0;
}

you will get what you want. 你会得到你想要的。

The compiler just warns you that you can face with similar conditions 编译器只是警告您可以面对类似的情况

All you need to do is add another member to your struct say Forename. 您需要做的就是在结构中添加另一个成员,例如Forename。

So if you has two persons with an ID of 63 but different forenames, are they equal or not? 所以,如果你有两个身份证号为63但姓名不同的人,他们是否相同?

All depends on what definition of "same" you want to implement. 一切都取决于你想要实现的“相同”的定义。

Use a better example struct, write a noddy applictaion to execute the various methods and see what happens when you change the definitions of equality and or equivalence, if they aren't all in step, you end up with things like !(a == b) != (a != b), which might be true, but if you don't override all the methods whoever uses you code will wonder what your intent was. 使用一个更好的示例结构,编写一个点头应用程序来执行各种方法,看看当你更改相等和/或等价的定义时会发生什么,如果它们不是一步一步,你最终会得到像!(a == b)!=(a!= b),这可能是真的,但是如果你不覆盖所有使用你的方法代码,你会想知道你的意图是什么。

Basically the compiler is telling you to be good citizen and make your intent clear. 基本上编译器告诉你要成为好公民,并明确你的意图。

可能是因为默认的Equals()方法对于真实系统来说不够好(例如在你的类中它应该比较ID字段)。

If you override Equals and GetHashCode you wouldn't even need to override the operators, and that's a cleaner approach. 如果你重写EqualsGetHashCode你甚至不需要覆盖运算符,这是一种更清洁的方法。 Edited: it should work since this is a struct. 编辑:它应该工作,因为这是一个结构。

Read the MSDN pages. 阅读MSDN页面。

CS0660 CS0660

CS0661 CS0661

The compiler is basically saying: "Since you are saying know how to compare your object, you should make it compare that way all the time." 编译器基本上是在说:“既然你说知道如何比较你的对象,你应该让它一直比较那样。”

public struct Coord
{
    public int x;
    public int y;

    public Coord(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public static bool operator ==(Coord c1, Coord c2)
    {
        return c1.x == c2.x && c1.y == c2.y;
    }

    public static bool operator !=(Coord c1, Coord c2)
    {
        return !(c1 == c2);
    }

    public bool Equals(Coord other)
    {
        return x == other.x && y == other.y;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is Coord && Equals((Coord) obj);
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

Here's an example. 这是一个例子。 Hopefully, it's helpful. 希望它有用。

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

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