Double的“==”运算符的定义

时间:2016-02-01 15:04:01

标签: c# .net language-lawyer

出于某种原因,我偷偷进入了课程Double的.NET Framework源代码,发现==的声明是:

public static bool operator ==(Double left, Double right) {
    return left == right;
}

同样的逻辑适用于每个运算符。

  • 这样的定义有什么意义?
  • 它是如何运作的?
  • 为什么不创建无限递归?

5 个答案:

答案 0 :(得分:62)

实际上,编译器会将==运算符转换为ceq IL代码,并且不会调用您提到的运算符。

源代码中运算符的原因很可能是因为它可以从C#以外的语言中调用,而不是直接将其转换为CEQ调用(或通过反射)。运算符 中的代码编译为CEQ,因此没有无限递归。

实际上,如果通过反射调用运算符,可以看到运算符被调用(而不是CEQ指令),显然不是无限递归(因为程序按预期终止):< / p>

double d1 = 1.1;
double d2 = 2.2;

MethodInfo mi = typeof(Double).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public );

bool b = (bool)(mi.Invoke(null, new object[] {d1,d2}));

产生的IL(由LinqPad 4编译):

IL_0000:  nop         
IL_0001:  ldc.r8      9A 99 99 99 99 99 F1 3F 
IL_000A:  stloc.0     // d1
IL_000B:  ldc.r8      9A 99 99 99 99 99 01 40 
IL_0014:  stloc.1     // d2
IL_0015:  ldtoken     System.Double
IL_001A:  call        System.Type.GetTypeFromHandle
IL_001F:  ldstr       "op_Equality"
IL_0024:  ldc.i4.s    18 
IL_0026:  call        System.Type.GetMethod
IL_002B:  stloc.2     // mi
IL_002C:  ldloc.2     // mi
IL_002D:  ldnull      
IL_002E:  ldc.i4.2    
IL_002F:  newarr      System.Object
IL_0034:  stloc.s     04 // CS$0$0000
IL_0036:  ldloc.s     04 // CS$0$0000
IL_0038:  ldc.i4.0    
IL_0039:  ldloc.0     // d1
IL_003A:  box         System.Double
IL_003F:  stelem.ref  
IL_0040:  ldloc.s     04 // CS$0$0000
IL_0042:  ldc.i4.1    
IL_0043:  ldloc.1     // d2
IL_0044:  box         System.Double
IL_0049:  stelem.ref  
IL_004A:  ldloc.s     04 // CS$0$0000
IL_004C:  callvirt    System.Reflection.MethodBase.Invoke
IL_0051:  unbox.any   System.Boolean
IL_0056:  stloc.3     // b
IL_0057:  ret 

有趣的是 - 对于整数类型,不存在相同的运算符(在参考源中或通过反射),只有SingleDoubleDecimalString,和DateTime,这反驳了我的理论,即存在从其他语言中调用的理论。显然你可以在没有这些算子的情况下将其他语言中的两个整数等同起来,所以我们回到问题&#34;为什么它们存在于double&#34;?

答案 1 :(得分:37)

这里的主要困惑是你假设所有.NET库(在这种情况下,扩展数值库,是BCL的一部分)都是用标准编写的C#。情况并非总是如此,不同的语言有不同的规则。

在标准C#中,由于操作符重载解析的工作方式,您看到的一段代码会导致堆栈溢出。但是,代码实际上并不在标准C#中 - 它基本上使用了C#编译器的未记录功能。它不会调用运算符,而是发出以下代码:

ldarg.0
ldarg.1
ceq
ret

那就是:)没有100%等效的C#代码 - 这在C#中根本不可能用你自己的类型。

即使这样,在编译C#代码时也不会使用实际的运算符 - 编译器会进行一系列优化,就像在这种情况下一样,只用简单{{1}替换op_Equality调用}}。同样,你不能在你自己的ceq结构中复制它 - 它的编译器魔法。

这当然不是.NET中的独特情况 - 有大量代码无效,标准C#。原因通常是(a)编译器黑客和(b)不同的语言,奇怪的(c)运行时黑客攻击(我看着你,DoubleEx!)。

由于Roslyn C#编译器是oepn源代码,我实际上可以指出你决定重载决策的地方:

The place where all binary operators are resolved

The "shortcuts" for intrinsic operators

当您查看快捷方式时,您会看到double和double之间的相等性导致内部double运算符, never 在该类型上定义的实际Nullable运算符中。 .NET类型系统必须假装==类似于任何其他类型,但C#不会 - Double是C#中的原语。

答案 2 :(得分:12)

原始类型的来源可能令人困惑。你见过Double结构的第一行吗?

通常你不能像这样定义一个递归结构:

public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}

原始类型在CIL中也有其原生支持。通常它们不会被视为面向对象的类型。如果在CIL中将其用作float64,则double只是一个64位值。但是,如果它作为通常的.NET类型处理,它包含一个实际值,它包含任何其他类型的方法。

所以你在这里看到的是与运营商相同的情况。通常,如果直接使用double类型,则永远不会调用它。 BTW,它的来源在CIL中看起来像这样:

.method public hidebysig specialname static bool op_Equality(float64 left, float64 right) cil managed
{
    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor()
    .custom instance void __DynamicallyInvokableAttribute::.ctor()
    .maxstack 8
    L_0000: ldarg.0
    L_0001: ldarg.1
    L_0002: ceq
    L_0004: ret
}

如您所见,没有无限循环(使用ceq工具而不是调用System.Double::op_Equality)。因此,当double被视为对象时,将调用operator方法,最终将其作为CIL级别的float64基元类型处理。

答案 3 :(得分:8)

我用JustDecompile查看了CIL。内部==被转换为CIL ceq操作码。换句话说,它是原始的CLR平等。

我很想知道C#编译器在比较两个double值时是否会引用ceq==运算符。在我提出的简单例子中(下面),它使用了ceq

这个程序:

void Main()
{
    double x = 1;
    double y = 2;

    if (x == y)
        Console.WriteLine("Something bad happened!");
    else
        Console.WriteLine("All is right with the world");
}

生成以下CIL(请注意带有标签IL_0017的语句):

IL_0000:  nop
IL_0001:  ldc.r8      00 00 00 00 00 00 F0 3F
IL_000A:  stloc.0     // x
IL_000B:  ldc.r8      00 00 00 00 00 00 00 40
IL_0014:  stloc.1     // y
IL_0015:  ldloc.0     // x
IL_0016:  ldloc.1     // y
IL_0017:  ceq
IL_0019:  stloc.2
IL_001A:  ldloc.2
IL_001B:  brfalse.s   IL_002A
IL_001D:  ldstr       "Something bad happened!"
IL_0022:  call        System.Console.WriteLine
IL_0027:  nop
IL_0028:  br.s        IL_0035
IL_002A:  ldstr       "All is right with the world"
IL_002F:  call        System.Console.WriteLine
IL_0034:  nop
IL_0035:  ret

答案 4 :(得分:-2)

如System.Runtime.Versioning命名空间的Microsoft文档中所示:此命名空间中的类型旨在用于.NET Framework,而不是用于用户应用程序.System.Runtime.Versioning命名空间包含高级类型支持.NET Framework并行实现的版本控制。