25

对于下面的代码

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); }
}

为什么编译器会给我这些警告?
不定义下面的方法有什么问题?

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()
4

8 回答 8

22

编辑:此答案已得到纠正,除其他事项外,请注意用户定义的值类型不会生成==,并提及ValueType.Equals.


一般来说,覆盖一个,但不是全部,是令人困惑的。用户不希望被覆盖,或者两者都被覆盖,具有相同的语义。

Microsoft对此状态的建议(除其他外):

  • 每当您实现 Equals 方法时,就实现 GetHashCode 方法。这使 Equals 和 GetHashCode 保持同步。

  • 每当您实现相等运算符 (==) 时,重写 Equals 方法,并使它们执行相同的操作。

在您的情况下,您有正当理由推迟Equals(编译器不会自动实现==)并仅覆盖这两个(==/ !=)。但是,仍然存在性能问题,因为ValueType.Equals使用反射:

“重写特定类型的 Equals 方法以提高方法的性能并更接近地表示该类型的相等概念。”

因此,仍然建议最后覆盖所有 ( ==/ !=/ Equals)。当然,对于这个微不足道的结构,性能可能并不重要。

于 2012-05-28T21:03:48.203 回答
7

框架内普遍期望某些操作应始终产生相同的结果。原因是某些操作(特别是排序和搜索,它们构成任何应用程序的很大一部分)依赖于这些不同的操作来产生有意义且一致的结果。在这种情况下,您打破了其中的几个假设:

  • 如果和==之间有一个有效的操作,它应该产生相同的结果aba.Equals(b)
  • 类似地,如果和!=之间有一个有效的操作,它应该产生相同的结果ab!a.Equals(b)
  • 如果两个对象ab存在,对于 which a == b,那么ab应该在存储在哈希表中时产生相同的键。

前两个,IMO,很明显;如果您要定义两个对象相等的含义,您应该包括可以检查两个对象是否相等的所有方法。请注意,编译器不会(通常,不能)强制您实际遵循这些规则。它不会对操作符的主体执行复杂的代码分析以查看它们是否已经模仿Equals,因为在最坏的情况下,这可能等同于解决停机问题。

但是,它可以做的是检查您最有可能违反这些规则的情况,特别是您提供了自定义比较运算符并且没有提供自定义Equals方法。这里的假设是,如果您不希望它们做一些特殊的事情,您就不会费心提供运算符,在这种情况下,您应该为所有需要同步的方法提供自定义行为。

如果您确实实现Equals了与编译器不同==的东西,则不会抱怨;你会达到 C# 愿意努力阻止你做一些愚蠢的事情的极限。它愿意阻止你在代码中意外引入细微的错误,但如果你想要的话,它会让你故意这样做。

第三个假设与框架中的许多内部操作使用哈希表的某种变体这一事实有关。如果我有两个对象,根据我的定义,“相等”,那么我应该能够做到这一点:

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

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

这是哈希表的一个基本属性,如果它突然不正确会导致非常奇怪的问题。

于 2012-05-28T22:10:23.330 回答
3

我的猜测是您收到这些警告,因为编译器不知道您Equals==方法中使用

假设你有这个实现

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; }
}

然后

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

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

b1 为,但 b2为

- 编辑 -

现在假设你想这样做

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

但这会引发类似 KeyNotFound 的异常

但是如果你添加

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

你会得到你想要的。

编译器只是警告您可能会遇到类似的情况

于 2012-05-28T21:44:04.737 回答
1

您需要做的就是将另一个成员添加到您的结构中,例如 Forename。

因此,如果您有两个 ID 为 63 但名字不同的人,他们是否相等?

一切都取决于您要实现的“相同”定义。

使用更好的示例结构,编写一个点头应用程序来执行各种方法,看看当你改变相等和/或等价的定义时会发生什么,如果它们不完全一致,你最终会得到像 !(a == b) != (a != b),这可能是真的,但是如果你不覆盖所有使用你的代码的方法,那么你的代码会想知道你的意图是什么。

基本上,编译器是在告诉你做个好公民并明确你的意图。

于 2012-05-28T21:49:46.570 回答
0

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

于 2012-05-28T21:03:15.087 回答
0

如果您覆盖Equals并且GetHashCode您甚至不需要覆盖运算符,那是一种更清洁的方法。编辑:它应该可以工作,因为这是一个结构。

于 2012-05-28T21:07:05.863 回答
0

阅读 MSDN 页面。

CS0660

CS0661

编译器基本上是在说:“既然你说知道如何比较你的对象,你应该让它一直这样比较。”

于 2012-05-28T21:07:47.650 回答
0
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;
    }
}

这是一个例子。希望它会有所帮助。

于 2018-04-06T09:45:26.203 回答