何时调用Double的==运算符?

Gis*_*shu 22 .net c#

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

Double a = Double.NaN;
Console.WriteLine(a == a); // => false
Console.WriteLine(a.Equals(a)); // => true
Run Code Online (Sandbox Code Playgroud)

以上似乎不对.a应始终为==自身(引用相等)并且两者应该是一致的.

看起来像Double重载==运算符.反射器确认如下:

[__DynamicallyInvokable]
public static bool operator ==(double left, double right)
{
    return (left == right);
}
Run Code Online (Sandbox Code Playgroud)

奇怪的是看起来递归并且没有提到NaN特定的行为.那为什么它会返回假?

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

var x = "abc";
var y = "xyz";
Console.WriteLine(x == y); // => false
Run Code Online (Sandbox Code Playgroud)

现在我明白了

    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)
Run Code Online (Sandbox Code Playgroud)
  • 对于双精度数,==运算符调用转换为ceqIL操作码
  • 对于字符串,它转换为System.String :: op_Equality(string,string).

果然,文档ceq指出它是特殊的浮点数和NaN.这解释了观察结果.

问题:

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

Dav*_*fer 11

反射器的错误解释

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

默认情况下,值类型不带有==实现.(用户定义的结构体是否继承了重载的==运算符?)但是,所有内置标量类型都有一个显式重载的运算符,编译器将其转换为适当的CIL.重载还包含一个简单的ceq基础比较,因此==运算符重载的动态/后期绑定/基于反射的调用不会失败.


更多细节

对于预定义的值类型,如果操作数的值相等,则相等运算符(==)返回true,否则返回false.对于除string之外的引用类型,如果其两个操作数引用同一对象,则==返回true.对于字符串类型,==比较字符串的值.

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

你所说的暗示==使用引用类型语义来比较a double.但是,由于double它是值类型,因此它使用值语义.这就是为什么3 == 3是真的,即使它们是不同的堆栈对象.

您几乎可以将此编译器转换视为LINQ的Queryable对象如何包含带有代码的扩展方法,但编译器会将这些调用转换为表达式树,而不是传递给LINQ提供程序.在这两种情况下,底层函数永远不会被调用.


Double的比较语义

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

如果通过调用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;
}
Run Code Online (Sandbox Code Playgroud)

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