【问题标题】:Strange behavior of Equals methodEquals 方法的奇怪行为
【发布时间】:2018-05-15 17:07:02
【问题描述】:

根据这个答案 https://stackoverflow.com/a/8315745/5324847 .Net 应该通过反射或按位比较值类型。但是,如果我们查看这段代码,我们会发现两者都不是(或者两者都不是??)。

//Bitwise test
//if .Net compares bitwise then last line should be false
//if it uses reflections then last line should be true
float a = -0.0f;
float b = 0.0f;
Console.WriteLine(string.Join("",BitConverter.GetBytes(a))); //prints 000128
Console.WriteLine(string.Join("",BitConverter.GetBytes(b))); //prints 0000
Console.WriteLine(a == b); //True
Console.WriteLine(a.Equals(b)); //prints True


//Reflection test
//if .Net compares using reflection then last line should be false
//if it uses bitwise comparsion then last line should be true
a = float.NaN;
b = float.NaN;
Console.WriteLine(string.Join("",BitConverter.GetBytes(a))); //prints 00192255
Console.WriteLine(string.Join("",BitConverter.GetBytes(b))); //prints 00192255
Console.WriteLine(a == b); //False
Console.WriteLine(a.Equals(b)); //prints True

如果我们将变量包装在结构中,.Net 使用按位比较。 证明:

public struct FloatS
{
    public float x; 
}
public static void Main()
{
    //bitwise test
    //if .Net compares bitwise then last line should be false
    //if it uses reflections then last line should be true
    FloatS a = new FloatS();
    FloatS b = new FloatS();
    a.x = -0.0f;
    b.x = 0.0f;
    Console.WriteLine(string.Join("",BitConverter.GetBytes(a.x))); //prints 000128
    Console.WriteLine(string.Join("",BitConverter.GetBytes(b.x))); //prints 0000
    Console.WriteLine(a.x == b.x); //True
    Console.WriteLine(a.Equals(b)); //prints False -- this time corectly because it's bitwise comparsion


    //Reflection test
    //if .Net compares using reflection then last line should be false
    //if it uses bitwise comparsion then last line should be true
    a.x = float.NaN;
    b.x = float.NaN;
    Console.WriteLine(string.Join("",BitConverter.GetBytes(a.x))); //prints 00192255
    Console.WriteLine(string.Join("",BitConverter.GetBytes(b.x))); //prints 00192255
    Console.WriteLine(a.x == b.x); //False
    Console.WriteLine(a.Equals(b)); //prints True - also corectly
}

我的假设是 .Net 对原始类型使用 Equals 的不同实现。但这似乎不正确,因为 float 和 struct 类型都可以转换为 System.ValueType

谁能解释发生了什么?

【问题讨论】:

  • 任何值类型都可以提供它自己的相等运算符和 Equals 方法的实现。如果您不这样做,您所描述的是“默认”行为。当然 float 和其他原始类型确实提供了这样的实现。
  • 很高兴知道@HansPassant。我刚刚意识到Console.WriteLine(float.NaN == float.NaN); print false,这对我来说太奇怪了!
  • 并检查reference source 并不能帮助我理解这种行为:@HansPassant,魔法发生的方法运算符是什么?
  • @GianPaolo 您不会在参考源中找到它,因为当您比较浮点数(或整数等)时 - in 被编译为特殊的 ceq IL 指令(或其他类似的指令,具体取决于上下文) ,而不是实际的 == 运算符。

标签: c# .net


【解决方案1】:

根据 ....NET 应该通过反射或按位比较值类型。

您误读了关于用户定义的值类型的答案。

  • 对于==,使用仅针对浮点数的特殊指令来比较浮点数。零和负零相等,NaN 不等于任何东西,甚至不等于自身,等等。

  • 任何类型都可以覆盖EqualsSystem.Single 可以这样做。浮点数上的x.Equals(y) 具有(x == y) || (IsNaN(x) && IsNaN(y)) 的语义。我不知道为什么Equals== 具有不同的行为,尽管可能是为了确保浮点数上的Equals 是等价关系,这对于某些应用程序是必要的,例如将浮点数放入哈希表。 (更新:我的假设是正确的;请参阅下面的评论。)

如果您编写用户定义的值类型并且不提供自己的Equals,那么您将获得默认值类型,正如您所注意到的那样有两个大问题:

  • 有时效率不高
  • 包装浮点数的结构不会自动获得浮点相等行为。更一般地说,包装 T 的结构不会自动获得 T 相等行为。

我的假设是 .NET 对原始类型使用 Equals 的不同实现。但这似乎不正确,因为 float 和 struct 类型都可以转换为 System.ValueType。

这一段对我来说是零意义,所以要么我误解了你,要么你相信一些关于继承的完全错误的东西。每种类型都可以覆盖Equals。这与用户代码可以装箱任何不可为空的值类型这一事实有什么关系?我看不出你这两个句子之间的联系,而且你相信有一个这一事实表明有人在这里有错误的信念。

【讨论】:

  • 我在参考资料中看到System.Single 还提供了== 运算符的实现(return left == right;)。实际使用此实现是否有任何情况(因此实际调用了此运算符)?还是只是为了满足编译器,还是为了一致性?
  • @Evk:好问题!编译器将完全忽略用户定义的运算符;所有用于处理内置类型算术运算的代码,包括重载解析和代码生成,在编译器中都是特例。这就是为什么您可以使用似乎在内置类型的无限递归中的== 运算符!那些用户定义的运算符在那里,这样编写自己的语言的人就不必像 C# 编译器团队那样做同样的工作。他们可以根据需要生成方法调用。
  • @Evk:更一般地说,对于基类库团队编译内置系统类型本身的情况,编译器中的许多正常规则都被放宽了。例如,struct Int32 { int value; } 应该是非法的,因为结构不能包含其自身,但如果编译器知道正在编译 mscorlib,则该规则将被关闭。
  • 谢谢,几天前我已经开始阅读你所有的 SO 答案,按投票排序,就在 10 分钟前,我找到了解释这种行为的人(以包含 int 的 int 示例):)
  • Ecma-355,partition I,第 8.2.5.2 章:“注意:尽管 IEC 60559:1989 定义两个浮点 NaN 始终比较为不相等,但 System.Object.Equals 的合同要求覆盖必须满足等价运算符的要求。因此,System.Double.Equals 和 System.Single.Equals 在比较两个 NaN 时返回 True,而在这种情况下,根据 IEC 标准的要求,等式运算符返回 False。"
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
  • 2021-07-07
相关资源
最近更新 更多