为什么我们必须在C#中定义==和!=?
C#编译器要求每当自定义类型定义运算符==
,它也必须定义!=
(请参见此处)。
为什么?
我很想知道为什么设计者认为它是必需的,为什么编译器默认为只有另一个运行时才有合理的实现。 例如,Lua允许您只定义相等运算符,并免费获得其他运算符。 C#可以通过要求你定义==或者== ==和!=然后自动编译缺少的!=运算符为!(left == right)
来做同样的事情。
我知道有些怪异的角落案例,其中一些实体可能既不平等又不平等(如IEEE-754 NaN's),但这些似乎是例外,而非规则。 所以这并不能解释为什么C#编译器设计者为此规则制定了例外规定。
我已经看到了定义相等运算符的做法差的情况,那么不平等运算符就是一个复制粘贴,每一个比较都反转,每一个&&转换到一个||。 (你明白了......基本上......(a == b)通过De Morgan的规则扩展了)。 这是一种糟糕的做法,编译器可以通过设计来消除,就像Lua一样。
注意:运算符<> <=> =也是一样。 我无法想象你需要以非自然的方式来定义这些情况。 Lua让你自然地通过前者的否定定义<和<=并且定义> =和>。 为什么C#不这样做(至少默认情况下是')?
编辑
显然,有正当理由允许程序员实现对平等和不平等的检查,不过他们喜欢。 有些答案指出了可能很好的情况。
然而,我的问题的核心就是为什么在C#中强制要求它通常不是逻辑上必需的?
与Object.Equals
, IEquatable.Equals
IEqualityComparer.Equals
等设计选择形成鲜明对比的是,缺少NotEquals
对象表明该框架认为!Equals()
对象不等并且就是这样。 此外,像Dictionary
这样的类和像.Contains()
这样的方法完全依赖于前面提到的接口,即使定义它们也不直接使用运算符。 实际上,当ReSharper生成相等成员时,它根据Equals()
定义==
和!=
,并且只有当用户选择生成运算符时。 框架不需要相等运算符来理解对象相等。
基本上,.NET框架不关心这些操作符,它只关心几个Equals
方法。 要求用户同时定义==和!=运算符的决定纯粹与语言设计相关,而与.NET相关的对象语义无关。
我不能为语言设计师说话,但从我可以推理的内容来看,它似乎是故意的,适当的设计决定。
看看这个基本的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编译的类。 很明显,它将使用我们的重载运算符==
因此,运行时使用什么!=
?
C#EMCA标准有一大堆规则(第14.9节),说明如何确定在评估相等性时使用哪个运算符。 如果要比较的类型是相同的类型,并且存在重载的等号运算符,则它将使用该重载,而不是从Object继承的标准引用相等运算符,因此将其过度简化并因此不完全准确。 那么,如果只有一个操作符存在,它就会使用默认的引用相等运算符,即所有对象都有,它没有超载。
了解到这种情况,真正的问题是:为什么这样设计,编译器为什么不自己搞清楚? 很多人都说这不是一个设计决定,但我喜欢认为它是这样想的,尤其是所有对象都有一个默认的相等运算符。
那么,编译器为什么不自动创建!=
运算符? 除非有人从微软证实这一点,否则我无法确定,但这是我可以从事实推断中确定的。
为了防止意外的行为
也许我想对==
进行值比较以测试相等性。 然而,当它来到!=
我根本不在乎,如果值相等,除非参考是平等的,因为我的程序认为它们是平等的,我只关心参考是否匹配。 毕竟,这实际上被概括为C#的默认行为(如果两个运算符都没有被重载,就像使用另一种语言编写一些.net库一样)。 如果编译器自动添加代码,我不再依赖编译器输出应该符合规范的代码。 编译器不应该写隐藏的代码来改变你的行为,特别是当你编写的代码在C#和CLI的标准之内时。
就它而言,它迫使你超载它,而不是去默认的行为,我只能坚定地说它是在标准(EMCA-334 17.9.2)2中。 该标准没有说明原因。 我相信这是由于C#借用了C ++的许多行为。 有关详情,请参阅下面的内容。
当你重写!=
和==
,你不必返回布尔值。
这是另一个可能的原因。 在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)
和Java,但这不是重点
可能是因为某人需要实现三值逻辑(即null
)。 在这种情况下(例如ANSI标准SQL),操作员不能简单地根据输入进行否定。
你可能会遇到以下情况:
var a = SomeObject();
而a == true
返回false
并且a == false
也返回false
。
除了C#在许多方面都遵循C ++之外,我能想到的最好的解释是在某些情况下,您可能希望采用稍微不同的方法来证明“不等于”而不是证明“相等”。
例如,显然通过字符串比较,当您看到不匹配的字符时,您可以测试相等性并return
循环。 然而,它可能并不那么干净,更复杂的问题。 想起布隆过滤器; 要快速判断元素是否在集合中很容易,但很难判断元素是否在集合中。 虽然可以使用相同的return
技术,但代码可能不尽如人意。
上一篇: Why must we define both == and != in C#?
下一篇: Make first letter of a string upper case (with maximum performance)