Double的"=="运算符的定义

Tho*_*oub 125 .net c# language-lawyer

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

public static bool operator ==(Double left, Double right) {
    return left == right;
}
Run Code Online (Sandbox Code Playgroud)

每个操作员都适用相同的逻辑.


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

D S*_*ley 62

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

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

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

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}));
Run Code Online (Sandbox Code Playgroud)

产生的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 
Run Code Online (Sandbox Code Playgroud)

有趣的是-同一运营商不存在(无论是在参考源或通过反射)的整数类型,只有Single,Double,Decimal,String,和DateTime,它们的存在是从其他语言中,它驳斥我的理论.显然,如果没有这些算子,你可以将其他语言中的两个整数等同起来,所以我们回到"它们为什么存在double"的问题?

  • 我能看到的唯一问题是C#语言规范说重载运算符优先于内置运算符.所以肯定的是,符合标准的C#编译器应该看到这里有一个重载的运算符并生成无限递归.嗯.令人不安. (12认同)
  • @DStanley - 我不否认生产的东西.我说我无法将其与语言规范相协调.这就是令人不安的问题.我正在考虑通过罗斯林仔细检查,看看我是否能在这里找到任何特殊处理,但我目前还没有很好地设置这个(错误的机器) (6认同)
  • 我不回答这个问题.它只解释了代码的翻译内容,而不是原因.根据*7.3.4 C#语言规范的二进制运算符重载决议*,我也期望无限递归.我假设参考源(http://referencesource.microsoft.com/#mscorlib/system/double.cs,1a65cbdb09544ba1)在这里并不真正适用. (5认同)

Lua*_*aan 37

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

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

ldarg.0
ldarg.1
ceq
ret
Run Code Online (Sandbox Code Playgroud)

就是这样:)没有100%等效的C#代码 - 这在C#中是不可能的,只有你自己的类型.

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

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

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

解析所有二元运算符的位置

内在运营商的"捷径"

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


Gyö*_*zeg 12

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

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

public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}
Run Code Online (Sandbox Code Playgroud)

原始类型在CIL中也有其原生支持.通常它们不会被视为面向对象的类型.如果float64在CIL中使用double,则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
}
Run Code Online (Sandbox Code Playgroud)

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


Dan*_*att 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");
}
Run Code Online (Sandbox Code Playgroud)

生成以下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
Run Code Online (Sandbox Code Playgroud)