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


126

由于某种原因,我偷偷摸摸该类的.NET Framework源代码Double,发现其中的声明==为:

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

相同的逻辑适用于每个操作员。


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

17
我期待无休止的递归。
HimBromBeere '16

5
我很确定它不用于双精度比较,而是ceq在IL中发布。只是为了满足某些文档目的,尽管找不到源。
哈比卜

2
因此很可能可以通过反射获得该运算符。
Damien_The_Unbeliever'2

3
那将永远不会被调用,编译器具有等式逻辑(ceq操作码),请参见何时调用Double的==运算符?
Alex K.

1
@ZoharPeled将双精度数除以零是有效的,并且将导致正或负无穷大。
Magnus

Answers:


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}));

产生的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,这否定了我的理论,他们的存在是从其他语言调用。显然,不用这些运算符,您就可以用其他语言将两个整数相等,因此我们回到了“为什么它们存在double”的问题?


12
我能看到的唯一问题是C#语言规范说重载的运算符优先于内置的运算符。因此,可以肯定的是,一个合格的C#编译器应该看到这里有一个重载的运算符,并且可以生成无限递归。嗯 烦人的。
Damien_The_Unbeliever'2

5
恕我直言,这不能回答问题。它仅说明代码被转换为什么,而不说明原因。根据C#语言规范的7.3.4二进制运算符重载解析,我也希望实现无限递归。我认为参考源(referencesource.microsoft.com/#mscorlib/system/…)在这里实际上并不适用。
德克·沃尔玛

6
@DStanley-我不否认产生了什么。我是说我无法与语言规范保持一致。那就是令人不安的。我当时正在考虑浏览罗斯林(Roslyn),看看是否可以在这里找到任何特殊的处理方法,但目前我做不到这一点(机器错误)
Damien_The_Unbeliever

1
@Damien_The_Unbeliever这就是为什么我认为它不是规范的例外,还是对“内置”运算符的不同解释。
D Stanley

1
由于@Jon Skeet尚未对此回答或评论,我怀疑这是一个bug(即违反规范)。
TheBlastOne '16

37

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

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

ldarg.0
ldarg.1
ceq
ret

就是这样:)没有100%等效的C#代码-使用您自己的类型在C#中根本无法实现。

即使这样,在编译C#代码时也不会使用实际的运算符-编译器进行了大量优化,例如在这种情况下,它op_Equality仅用simple 代替了调用ceq。同样,您不能在自己的DoubleEx结构中复制它-这是编译器的魔力。

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

由于Roslyn C#编译器是oepn源,因此我实际上可以将您指向确定重载解决方案的地方:

解析所有二进制运算符的地方

内在运算符的“捷径”

当查看快捷方式时,您会看到double和double之间的相等导致内部double运算符,而不是==类型上定义的实际运算符。.NET类型系统必须假装Double为其他类型,但C#不需要- double是C#中的原始类型。


1
不确定我是否同意参考源中的代码只是“反向工程”。该代码具有编译器指令#if和其他不会在已编译代码中出现的构件。另外,如果它被反向工程为double那么为什么不是反向工程的int还是long?我确实认为源代码是有原因的,但我认为==将操作符内部的用法编译为a CEQ可以防止递归。由于该运算符是该类型的“预定义”运算符(并且不能被覆盖),因此不适用重载规则。
D Stanley

@DStanley我不想暗示所有代码都是反向工程。再一次,double它不是BCL的一部分-它在一个单独的库中,恰好包含在C#规范中。是的,将==gets编译为ceq,但这仍然意味着这是您无法在自己的代码中复制的编译器技巧,并且它不是C#规范的一部分(就像struct 的float64字段一样Double)。它不是C#的约定部分,因此即使将其用C#编译器进行编译,也没有什么意义。
a安

@DStanely我找不到真正的框架是如何组织的,但是在.NET 2.0的参考实现中,所有棘手的部分都只是用C ++实现的编译器内部函数。当然,仍然有很多.NET本机代码,但是诸如“比较两个双精度”之类的事情在纯.NET中并不能很好地工作。这是BCL中不包含浮点数的原因之一。就是说,代码以(非标准)C#实现,可能正是出于您前面提到的原因-确保其他.NET编译器可以将这些类型视为真实的.NET类型。
六安'16

@DStanley但好吧,要点。我删除了“反向工程”参考,并改写了答案,以明确提及“标准C#”,而不仅仅是C#。并且不要doubleintlong- 相同的方式对待,int并且long它们是所有 .NET语言都必须支持的原始类型。floatdecimaldouble不是。
a安

12

基本类型的来源可能令人困惑。您是否看到过Double结构的第一行?

通常,您不能像这样定义递归结构:

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

原始类型在CIL中也有其本机支持。通常,它们不会像面向对象的类型那样对待。如果将double用作64位值float64在CIL中。但是,如果将其作为常规.NET类型进行处理,则它将包含实际值,并且包含与其他任何类型一样的方法。

因此,对于运营商,您在这里看到的情况是相同的。通常,如果直接使用double类型,则永远不会调用它。顺便说一句,其来源在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方法,该方法最终将其作为float64CIL级别上的原始类型进行处理。


1
对于那些不了解本文第一部分的人(可能是因为他们通常不编写自己的值类型),请尝试使用代码public struct MyNumber { internal MyNumber m_value; }。当然,它不能被编译。错误为错误CS0523:类型为'MyNumber'的结构成员'MyNumber.m_value'导致结构布局中出现循环
Jeppe Stig

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(注意带有label的语句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

-2

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


System.Runtime.Versioning什么关系System.Double
Koopakiller
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.