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

拜拜、爱过 提交于 2020-03-04 13:48:01

C#编译器要求,每当自定义类型定义运算符== ,它还必须定义!= (请参见此处 )。

为什么?

我很好奇,为什么设计师会认为这是必要的,以及为什么当仅存在另一个运算符时,编译器为何不能默认其中一个运算符为合理的实现。 例如,Lua允许您仅定义相等运算符,而另一个则免费。 C#可以通过要求您定义==或同时定义==和!=来完成相同的工作,然后将缺少的!=运算符自动编译为!(left == right)

我知道有些情况下有些实体可能不相等或不相等(例如IEEE-754 NaN),但是在某些特殊情况下,这似乎是个例外,而不是规则。 因此,这不能解释为什么C#编译器设计人员将例外作为规则。

我已经看到了定义平等运算符的工艺不佳的情况,然后不平等运算符是一个复制粘贴,每个比较都相反,每个&&切换为||。 (您明白了……基本上!(a == b)通过De Morgan的规则扩展了)。 与Lua的情况一样,编译器可以通过设计消除这种糟糕的做法。

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

编辑

显然,有充分的理由允许程序员执行他们喜欢的相等性和不平等性检查。 一些答案指出了可能不错的情况。

但是,我的问题的核心是,为什么在C#中通常逻辑上不必要时强制执行此操作?

它与.NET接口(如Object.EqualsIEquatable.Equals IEqualityComparer.Equals设计选择形成鲜明对比,在缺少NotEquals对应项的情况下,该框架认为!Equals()对象是不相等的,仅此NotEquals 。 此外,诸如Dictionary类的类和诸如.Contains()类的方法.Contains()取决于上述接口,即使定义了它们也不会直接使用运算符。 实际上,当ReSharper生成相等成员时,它会根据Equals()定义==!= ,并且即使在用户选择完全生成运算符的情况下也是如此。 框架不需要相等运算符来了解对象相等。

基本上,.NET框架不关心这些运算符,而仅关心一些Equals方法。 要求用户同时定义==和!=运算符的决定完全与语言设计有关,而就.NET而言,与对象语义无关。


#1楼

可能只是他们没有想到的事情而没有时间去做。

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

您是对的,只需少量工作,编译器便可以免费将其提供给我们。


#2楼

好吧,这可能只是设计选择,但是正如您所说, x!= y不必与!(x == y) 。 通过不添加默认实现,可以确保您不会忘记实现特定实现。 而且,如果确实如您所说的那样琐碎,则可以仅使用另一个实现。 我看不出这是“不良做法”。

C#和Lua之间可能还有其他差异...


#3楼

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

您可能遇到以下情况:

var a = SomeObject();

a == true返回falsea == false也返回false


#4楼

除了C#在许多方面适合C ++之外,我能想到的最好解释是,在某些情况下,您可能想采用稍微不同的方法来证明“不平等”而不是证明“平等”。

显然,用字符串比较,例如,你可以只测试平等和return循环了,当你看到非匹配的字符。 但是,它可能没有那么复杂的问题。 我想到了绽放过滤器 ; 快速判断元素是否不在集合中非常容易,但是很难判断元素是否在集合中。 尽管可以使用相同的return技术,但代码可能并不那么漂亮。


#5楼

如果您在.net源代码中查看==和!=重载的实现,则它们通常不会将!=实现为!(左==右)。 他们使用否定逻辑完全实现了它(例如==)。 例如,DateTime将==实现为

return d1.InternalTicks == d2.InternalTicks;

和!= as

return d1.InternalTicks != d2.InternalTicks;

如果您(或编译器(如果隐式执行))将实现=

return !(d1==d2);

那么您将在类引用中对==和!=的内部实现进行假设。 避免这种假设可能是他们决定背后的哲学。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!