c#编译器要求每当自定义类型定义operator ==时,它也必须定义!=(参见这里)。

Why?

我很好奇为什么设计人员认为这是必要的,为什么编译器不能默认为一个合理的实现操作符时,只有另一个存在。例如,Lua只允许定义相等操作符,而免费获得另一个。c#也可以做到这一点,要求你定义==或同时定义==和!=,然后自动将缺少的!=运算符编译为!(左==右)。

我知道有一些奇怪的极端情况,一些实体可能既不相等也不相等(如IEEE-754 NaN),但这些似乎是例外,而不是规则。因此,这并不能解释为什么c#编译器设计人员将例外设置为规则。

我见过一些糟糕的情况,其中定义了相等运算符,然后不等式运算符是一个复制粘贴,每个比较都反转,每个&&切换到||(你明白了吧……(a==b)通过德摩根法则展开)。这是编译器可以通过设计消除的不良实践,就像Lua一样。

注意: 运算符< > <= >=也是如此。我无法想象在哪些情况下需要用非自然的方式来定义它们。Lua只允许您定义<和<=,并通过前两者的否定自然地定义>=和>。为什么c#不做同样的事情(至少在默认情况下)?

EDIT

显然,有充分的理由允许程序员根据自己的喜好执行相等和不相等的检查。一些答案指向了这样做可能不错的案例。

然而,我问题的核心是,为什么在c#中这是强制要求的,而通常这在逻辑上是不必要的?

It is also in striking contrast to design choices for .NET interfaces like Object.Equals, IEquatable.Equals IEqualityComparer.Equals where the lack of a NotEquals counterpart shows that the framework considers !Equals() objects as unequal and that's that. Furthermore, classes like Dictionary and methods like .Contains() depend exclusively on the aforementioned interfaces and do not use the operators directly even if they are defined. In fact, when ReSharper generates equality members, it defines both == and != in terms of Equals() and even then only if the user chooses to generate operators at all. The equality operators aren't needed by the framework to understand object equality.

基本上,. net框架并不关心这些操作符,它只关心一些Equals方法。要求用户同时定义==和!=操作符的决定纯粹与语言设计有关,而与. net关心的对象语义无关。


当前回答

要回答您的编辑,关于为什么如果您覆盖了一个,就必须覆盖两个,这都在继承中。

If you override ==, most likely to provide some sort of semantic or structural equality (for instance, DateTimes are equal if their InternalTicks properties are equal even through they may be different instances), then you are changing the default behavior of the operator from Object, which is the parent of all .NET objects. The == operator is, in C#, a method, whose base implementation Object.operator(==) performs a referential comparison. Object.operator(!=) is another, different method, which also performs a referential comparison.

In almost any other case of method overriding, it would be illogical to presume that overriding one method would also result in a behavioral change to an antonymic method. If you created a class with Increment() and Decrement() methods, and overrode Increment() in a child class, would you expect Decrement() to also be overridden with the opposite of your overridden behavior? The compiler can't be made smart enough to generate an inverse function for any implementation of an operator in all possible cases.

However, operators, though implemented very similarly to methods, conceptually work in pairs; == and !=, < and >, and <= and >=. It would be illogical in this case from the standpoint of a consumer to think that != worked any differently than ==. So, the compiler can't be made to assume that a!=b == !(a==b) in all cases, but it's generally expected that == and != should operate in a similar fashion, so the compiler forces you to implement in pairs, however you actually end up doing that. If, for your class, a!=b == !(a==b), then simply implement the != operator using !(==), but if that rule does not hold in all cases for your object (for instance, if comparison with a particular value, equal or unequal, is not valid), then you have to be smarter than the IDE.

真正的问题应该问是为什么<、>、< =和> =是对比较操作符必须同时实现,当在数值上! (b < b) = = > =和! (a > b) = = < = b。你应该需要实现所有四个如果你覆盖,你应该需要重写= =(和! =),因为(< = b) = = (a = = b)如果一个语义等于b。

其他回答

编程语言是对异常复杂的逻辑语句的语法重排。考虑到这一点,你能定义一个相等的情况而不定义一个不相等的情况吗?答案是否定的。如果一个物体a等于物体b,那么物体a的逆不等于b也必须成立。另一种表达方式是

如果a == b那么!(a != b)

这为语言确定对象的相等性提供了明确的能力。例如,比较NULL != NULL可能会对没有实现非相等语句的相等系统的定义产生不利影响。

现在,关于!=简单地是可替换的定义

如果!(a==b)那么a!= b

我不能反驳这一点。然而,这很可能是c#语言规范组的决定,程序员被迫显式地定义对象的相等性和非相等性

这是我首先想到的:

如果测试不平等比测试平等快得多呢? 如果在某些情况下,你想同时返回false for ==和!=(也就是说,如果它们由于某种原因不能进行比较)

要回答您的编辑,关于为什么如果您覆盖了一个,就必须覆盖两个,这都在继承中。

If you override ==, most likely to provide some sort of semantic or structural equality (for instance, DateTimes are equal if their InternalTicks properties are equal even through they may be different instances), then you are changing the default behavior of the operator from Object, which is the parent of all .NET objects. The == operator is, in C#, a method, whose base implementation Object.operator(==) performs a referential comparison. Object.operator(!=) is another, different method, which also performs a referential comparison.

In almost any other case of method overriding, it would be illogical to presume that overriding one method would also result in a behavioral change to an antonymic method. If you created a class with Increment() and Decrement() methods, and overrode Increment() in a child class, would you expect Decrement() to also be overridden with the opposite of your overridden behavior? The compiler can't be made smart enough to generate an inverse function for any implementation of an operator in all possible cases.

However, operators, though implemented very similarly to methods, conceptually work in pairs; == and !=, < and >, and <= and >=. It would be illogical in this case from the standpoint of a consumer to think that != worked any differently than ==. So, the compiler can't be made to assume that a!=b == !(a==b) in all cases, but it's generally expected that == and != should operate in a similar fashion, so the compiler forces you to implement in pairs, however you actually end up doing that. If, for your class, a!=b == !(a==b), then simply implement the != operator using !(==), but if that rule does not hold in all cases for your object (for instance, if comparison with a particular value, equal or unequal, is not valid), then you have to be smarter than the IDE.

真正的问题应该问是为什么<、>、< =和> =是对比较操作符必须同时实现,当在数值上! (b < b) = = > =和! (a > b) = = < = b。你应该需要实现所有四个如果你覆盖,你应该需要重写= =(和! =),因为(< = b) = = (a = = b)如果一个语义等于b。

如果你看一下。net源代码中==和!=重载的实现,它们通常不会实现!= as !(左==右)。他们用否定的逻辑完全实现它(比如==)。例如,DateTime实现了== as

return d1.InternalTicks == d2.InternalTicks;

And != as

return d1.InternalTicks != d2.InternalTicks;

如果你(或编译器,如果它隐式地做)要实现!= as

return !(d1==d2);

然后,您要对类引用的对象中==和!=的内部实现做一个假设。避免这种假设可能是他们决定背后的哲学。

我不能代表语言设计师说话,但从我的推理来看,这似乎是有意为之,合理的设计决策。

看看这个基本的f#代码,你可以把它编译成一个工作库。这是f#的合法代码,只重载了等号运算符,而不是不等式运算符:

module Module1

type Foo() =
    let mutable myInternalValue = 0
    member this.Prop
        with get () = myInternalValue
        and set (value) = myInternalValue <- value

    static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
    //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

这就是它看起来的样子。它只在==上创建一个相等比较器,并检查类的内部值是否相等。

虽然不能在c#中创建这样的类,但可以使用为。net编译的类。很明显,它将使用重载操作符for ==那么,运行时使用什么!=?

The C# EMCA standard has a whole bunch of rules (section 14.9) explaining how to determine which operator to use when evaluating equality. To put it overly-simplified and thus not perfectly accurate, if the types that are being compared are of the same type and there is an overloaded equality operator present, it will use that overload and not the standard reference equality operator inherited from Object. It is no surprise, then, that if only one of the operators is present, it will use the default reference equality operator, that all objects have, there is not an overload for it.1

知道了这种情况后,真正的问题是:为什么要这样设计,为什么编译器不自己解决这个问题?很多人都说这不是一个设计决策,但我喜欢这样认为,特别是考虑到所有对象都有一个默认的相等运算符。

那么,为什么编译器不自动创建!=操作符呢?我不能确定,除非微软的人证实这一点,但这是我可以从事实推理得出的结论。


防止意外行为

Perhaps I want to do a value comparison on == to test equality. However, when it came to != I didn't care at all if the values were equal unless the reference was equal, because for my program to consider them equal, I only care if the references match. After all, this is actually outlined as default behavior of the C# (if both operators were not overloaded, as would be in case of some .net libraries written in another language). If the compiler was adding in code automatically, I could no longer rely on the compiler to output code that should is compliant. The compiler should not write hidden code that changes the behavior of yours, especially when the code you've written is within standards of both C# and the CLI.

至于它迫使你重载它,而不是去默认的行为,我只能坚定地说,它是在标准(EMCA-334 17.9.2)2。该标准没有具体说明原因。我相信这是因为c#从c++中借鉴了很多行为。有关这方面的更多信息,请参见下文。


当你重写!=和==时,你不必返回bool。

这是另一个可能的原因。在c#中,这个函数:

public static int operator ==(MyClass a, MyClass b) { return 0; }

和这个一样有效

public static bool operator ==(MyClass a, MyClass b) { return true; }

如果返回的不是bool类型,编译器就不能自动推断出相反的类型。此外,在你的操作符确实返回bool值的情况下,创建只存在于特定情况下的生成代码没有意义,或者像我上面说的,隐藏CLR默认行为的代码。


c#大量借鉴了c++ 3

当c#被引入时,在MSDN杂志上有一篇文章是这样谈论c#的:

许多开发人员希望有一种像Visual Basic一样易于编写、阅读和维护的语言,但它仍然提供了c++的强大功能和灵活性。

是的,c#的设计目标是提供几乎与c++相同的功能,仅牺牲了一点点方便,如严格的类型安全和垃圾收集。c#是完全模仿c++的。

在c++中,相等操作符不必返回bool值,正如这个示例程序所示,对此您可能不会感到惊讶

现在,c++并不直接要求重载补充操作符。如果编译示例程序中的代码,您将看到它运行时没有错误。然而,如果你尝试添加一行:

cout << (a != b);

你会得到

编译器错误C2678 (MSVC):二进制'!=':没有找到左操作数为'Test'类型的操作符(或者没有可接受的转换)'。

因此,虽然c++本身不要求您成对重载,但它不允许您在自定义类上使用未重载的相等操作符。它在。net中是有效的,因为所有对象都有一个默认的对象;c++则不然。


1. 顺便说一句,如果您想重载其中一个操作符,c#标准仍然要求您重载这对操作符。这是标准的一部分,而不仅仅是编译器。然而,当您访问用另一种语言编写的.net库时,关于调用哪个操作符的确定规则也适用,而这种语言没有相同的要求。

2. EMCA-334 (pdf) (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf)

3.还有Java,但这不是重点