为什么我们必须同时定义==和!=在C#中?

为什么我们必须同时定义==和!=在C#中?,c#,language-design,C#,Language Design,C#编译器要求无论何时自定义类型定义运算符==,它都必须定义=(请参阅) 为什么? 我很想知道为什么设计人员认为这是必要的,为什么编译器不能在只有另一个操作符存在的情况下默认为其中一个操作符的合理实现。例如,Lua允许您仅定义相等运算符,而免费获得另一个运算符。C#也可以通过要求您定义==或同时定义==和!=然后自动编译缺少的!=操作员身份!(左==右) 我知道有些奇怪的情况,有些实体可能既不平等也不平等(如IEEE-754 NaN),但这些似乎是例外,而不是规则。因此,这并不能解释为什么C#编

C#编译器要求无论何时自定义类型定义运算符
==
,它都必须定义
=(请参阅)

为什么?

我很想知道为什么设计人员认为这是必要的,为什么编译器不能在只有另一个操作符存在的情况下默认为其中一个操作符的合理实现。例如,Lua允许您仅定义相等运算符,而免费获得另一个运算符。C#也可以通过要求您定义==或同时定义==和!=然后自动编译缺少的!=操作员身份<代码>!(左==右)

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

我看到过一些粗制滥造的例子,其中定义了等式运算符,然后不等式运算符是一个复制粘贴,每个比较都是反向的,每个&&都切换到a | |(你得到了点…基本上!(a==b)通过De Morgan规则展开)。这是编译器可以通过设计消除的糟糕实践,就像Lua一样

注: 对于运算符<>=,情况也是如此。我无法想象你需要用不自然的方式来定义这些。Lua允许您通过前面的否定,自然地定义<和=和>。为什么C不这样做(至少“默认情况下”)呢

编辑

显然,有充分的理由允许程序员以自己喜欢的方式实现平等和不平等检查。一些答案指出了这样做可能很好的情况

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

它与.NET接口的设计选择也形成了鲜明的对比,如
Object.Equals
IEquatable.Equals
IEqualityComparer.Equals
,其中缺少
NotEquals
对应项表明框架考虑了
!Equals()
对象是不相等的,就是这样。此外,类如
Dictionary
和方法如
Contains()
完全依赖于前面提到的接口,即使定义了操作符,也不直接使用它们。事实上,当ReSharper生成相等成员时,它同时定义了
==
=
Equals()
方面,并且仅当用户选择生成运算符时。框架不需要相等运算符来理解对象相等


基本上,.NET framework不关心这些运算符,它只关心少数
Equals
方法。同时要求==和!=的决定要由用户串联定义的运算符纯粹与语言设计有关,而不是与.NET有关的对象语义。

可能只是一些他们没有想到但没有时间做的事情

当我重载==时,我总是使用您的方法。然后我就用在另一个


你说得对,只需少量的工作,编译器就可以免费提供给我们。

好吧,这可能只是一种设计选择,但正如你所说,
x!=y
不必与
相同!(x==y)
。通过不添加默认实现,可以确定您不会忘记实现特定的实现。如果它确实像您所说的那样微不足道,那么您可以使用另一个实现一个。我不认为这是“拙劣的做法”


C#和Lua之间可能还有其他一些区别…

可能是因为某人需要实现三值逻辑(即
null
)。在这种情况下,例如ANSI标准SQL,运算符不能简单地根据输入求反

您可以有一个案例,其中:

var a = SomeObject();
< > >代码> A>=Trase>代码>返回>代码> false 和 A==false >还返回<代码> false

< P>除C++在许多领域中都遵从C++之外,我能想到的最好解释是,在某些情况下,您可能需要采取一种稍微不同的方法来证明“不平等”,而不是证明“相等”。p>
例如,显然,通过字符串比较,当您看到不匹配的字符时,您可以测试是否相等,并返回循环外的
。然而,对于更复杂的问题,它可能没有那么干净。我想到了这个问题;很容易快速判断元素是否不在集合中,但很难判断元素是否在集合中。虽然可以应用相同的
return
技术,但代码可能没有那么漂亮。

如果您查看==和!=在.net源代码中,它们通常不实现!=作为!(左==右)。他们用否定逻辑完全实现它(比如==)。例如,DateTime实现==as

return d1.InternalTicks == d2.InternalTicks;
还有!=作为

return d1.InternalTicks != d2.InternalTicks;
如果您(或隐式执行此操作的编译器)要实现!=作为

return !(d1==d2);

然后,您对==和!=的内部实现进行假设在类引用的内容中。避免这种假设可能是他们决策背后的理念。

编程语言是异常复杂的逻辑语句的语法重新排列。考虑到这一点,你能定义平等的情况而不定义不平等的情况吗?答案是否定的。如果一个物体a等于物体b,那么物体a的倒数不等于b也必须为真。另一种方式是

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

这为语言提供了确定对象相等性的明确能力。例如,比较值为NULL!=NULL可能会破坏不实现非相等语句的相等系统的定义

现在,关于!=简单地说是可替换的
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
public static int operator ==(MyClass a, MyClass b) { return 0; }
public static bool operator ==(MyClass a, MyClass b) { return true; }
cout << (a != b);