【问题标题】:When is Double's == operator invoked?何时调用 Double 的 == 运算符?
【发布时间】:2013-02-04 03:58:04
【问题描述】:

这一切都始于有人向我提出的一个诡计问题..(书中提到 - 简而言之 C#)这是它的要点。

Double a = Double.NaN;
Console.WriteLine(a == a); // => false
Console.WriteLine(a.Equals(a)); // => true

上面的内容似乎不对。 a 应该始终是 == 自身(引用相等)并且两者都应该是一致的。

似乎 Double 重载了 == 运算符。经反射器确认如下:

[__DynamicallyInvokable]
public static bool operator ==(double left, double right)
{
    return (left == right);
}

奇怪的看起来是递归的,没有提到 NaN 特定的行为。那么为什么它返回false呢?

所以我再添加一些代码来区分

var x = "abc";
var y = "xyz";
Console.WriteLine(x == y); // => false

现在我明白了

    L_0001: ldc.r8 NaN
    L_000a: stloc.0 
    L_000b: ldloc.0 
    L_000c: ldloc.0 
    L_000d: ceq 
    L_000f: call void [mscorlib]System.Console::WriteLine(bool)
    L_0014: nop 
    L_0015: ldloca.s a
    L_0017: ldloc.0 
    L_0018: call instance bool [mscorlib]System.Double::Equals(float64)
    L_001d: call void [mscorlib]System.Console::WriteLine(bool)
    L_0022: nop 
    L_0023: ldstr "abc"
    L_0028: stloc.1 
    L_0029: ldstr "xyz"
    L_002e: stloc.2 
    L_002f: ldloc.1 
    L_0030: ldloc.2 
    L_0031: call bool [mscorlib]System.String::op_Equality(string, string)
    L_0036: call void [mscorlib]System.Console::WriteLine(bool)
  • 对于双精度数,== 运算符调用转换为ceq IL 操作码
  • 对于字符串,它转换为 System.String::op_Equality(string, string)。

果然documentation for ceq 指定它是浮点数和NaN 的特殊情况。这解释了观察结果。

问题:

  • 为什么在 Double 上定义 op_Equality? (并且实现不考虑 NaN 特定行为)
  • 什么时候调用?

【问题讨论】:

  • Reflector 在这种情况下经常失败。我猜operator == 不会调用自己,而是在内部使用ceq
  • 我猜这和stackoverflow.com/q/14458890/1236044是同一个问题
  • @jbl- 这个问题是问为什么 2 equals 返回不同的结果——我似乎已经弄清楚了。我的问题是关于似乎从未调用过的看似多余的静态 op_equality 实现。

标签: c# .net


【解决方案1】:

反映者的错误解释

您从 Reflector 看到的反编译实际上是 Reflector 中的一个错误。 Reflector 需要能够反编译比较两个双精度值的函数;在这些函数中,您会发现ceq 直接发送到代码中。因此,Reflector 将 ceq 指令解释为两个双精度之间的 ==,以帮助反编译一个正在比较两个双精度的函数。

默认情况下,值类型不带有 == 实现。 (Don't user-defined structs inherit an overloaded == operator?) 但是,所有内置标量类型都有一个显式重载的运算符,编译器将其翻译 为适当的 CIL。重载还包含一个简单的基于 ceq 的比较,因此 == 运算符重载的动态/后期绑定/基于反射的调用不会失败。


更多详情

对于预定义的值类型,相等运算符 (==) 在以下情况下返回 true 其操作数的值相等,否则为假。以供参考 字符串以外的类型,如果两个操作数引用 ==,则返回 true 同一个对象。对于字符串类型,== 比较 字符串。

-- http://msdn.microsoft.com/en-us/library/53k8ybth.aspx

您所说的意味着 == 使用引用类型语义来比较 double。然而,由于double 是一个值类型,它使用值语义。这就是为什么3 == 3 是正确的,即使它们是不同的堆栈对象。

您几乎可以将此编译器翻译视为 LINQ 的 Queryable 对象如何在其中包含扩展方法代码,但编译器将这些调用翻译成表达式树,然后传递给 LINQ 提供程序。在这两种情况下,底层函数都不会真正被调用。


Double 的比较语义

Double 的文档确实提到了 ceq CIL 指令的工作原理:

如果通过调用 Equals 方法测试两个 Double.NaN 值是否相等,则该方法返回 true。但是,如果使用相等运算符测试两个 NaN 值是否相等,则运算符返回 false。当您想确定 Double 的值是否不是数字 (NaN) 时,另一种方法是调用 IsNaN 方法。

-- http://msdn.microsoft.com/en-us/library/ya2zha7s.aspx


原始编译器源代码

如果您查看反编译的 C# 编译器源代码,您会发现以下代码可将双重比较直接转换为 ceq

private void EmitBinaryCondOperator(BoundBinaryOperator binOp, bool sense)
{
    int num;
    ConstantValue constantValue;
    bool flag = sense;
    BinaryOperatorKind kind = binOp.OperatorKind.OperatorWithLogical();
    if (kind <= BinaryOperatorKind.GreaterThanOrEqual)
    {
        switch (kind)
        {
            ...

            case BinaryOperatorKind.Equal:
                goto Label_0127;

            ...
        }
    }
...
Label_0127:
    constantValue = binOp.Left.ConstantValue;
    if (((constantValue != null) && constantValue.IsPrimitiveZeroOrNull) && !constantValue.IsFloating)
    {
        ...
        return;
    }
    constantValue = binOp.Right.ConstantValue;
    if (((constantValue != null) && constantValue.IsPrimitiveZeroOrNull) && !constantValue.IsFloating)
    {
        ...
        return;
    }
    this.EmitBinaryCondOperatorHelper(ILOpCode.Ceq, binOp.Left, binOp.Right, sense);
    return;
}

以上代码来自Roslyn.Compilers.CSharp.CodeGen.CodeGenerator.EmitBinaryCondOperator(...),我添加了“...”以使代码更具可读性。

【讨论】:

  • 很好的详细答案。因此,总结一下您的回答, Double::op_equality 运算符被调用,但编译器将其“内联”到其实现,即 ceq 操作码。 Reflector 错误地将 ceq 操作代码反编译为 == 调用。对吗?
  • 选词错误——我的错。我所说的“引用相等”是指一个对象必须始终 == 对自身。所以instance1 == instance1 在几乎所有情况下都成立,因为它们都指向同一个(refType/valueType)对象。 (除了 Double.Nan - 这是出于数学原因的规则例外)
  • @Gishu Perfect tl;dr version :)
【解决方案2】:

来自 msdn:http://msdn.microsoft.com/en-us/library/ya2zha7s.aspx

如果通过调用 Equals 来测试两个 Double.NaN 值是否相等 方法,该方法返回true。但是,如果测试了两个 NaN 值 对于使用相等运算符的相等,运算符返回 错误的。当你想判断一个 Double 的值是否不是 一个数字 (NaN),另一种方法是调用 IsNaN 方法。

【讨论】:

    【解决方案3】:

    msdn 中声明;

    如果通过调用 Equals 来测试两个 Double.NaN 值是否相等 方法,该方法返回true。但是,如果测试了两个 NaN 值 对于使用相等运算符的相等,运算符返回 错误的。当你想判断一个 Double 的值是否不是 一个数字 (NaN),另一种方法是调用 IsNaN 方法。

    这是故意这样做以符合 IEC 60559:1989,因为它声明两个 NaN 值不相等,因为它们不被视为数字,因此 op_Equal 定义符合此标准;

    根据 IEC 60559:1989,两个浮点数的值为 NaN 永远不相等。然而,根据规范 System.Object::Equals 方法,最好重写此方法 提供值相等语义。由于 System.ValueType 提供 此功能通过使用反射,描述为 Object.Equals 特别指出值类型应该考虑 覆盖默认的 ValueType 实现以获得性能 增加。其实从源头上看 System.ValueType::Equals(clr\src\BCL\System\ValueType.cs 的第 36 行 在 SSCLI 中),甚至有来自 CLR Perf 团队的评论 System.ValueType::Equals 不快的效果。

    参考:http://blogs.msdn.com/b/shawnfa/archive/2004/07/19/187792.aspx

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-15
      • 1970-01-01
      • 2016-05-10
      • 2015-12-13
      • 1970-01-01
      • 2013-01-03
      相关资源
      最近更新 更多