首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Double "==“运算符的定义

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

Stack Overflow用户
提问于 2016-02-01 23:04:01
回答 5查看 5.3K关注 0票数 127

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

代码语言:javascript
复制
public static bool operator ==(Double left, Double right) {
    return left == right;
}

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

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

回答 5

Stack Overflow用户

回答已采纳

发布于 2016-02-01 23:22:30

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

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

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

代码语言:javascript
复制
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编译):

代码语言:javascript
复制
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 

有趣的是-整型类型不存在相同的运算符(无论是在参考源中还是通过反射),只有SingleDoubleDecimalStringDateTime,这反驳了我的理论,即它们存在是为了从其他语言调用。显然,在没有这些运算符的情况下,您可以在其他语言中将两个整数等同起来,因此我们又回到了“为什么它们存在于double”的问题上。

票数 62
EN

Stack Overflow用户

发布于 2016-02-02 00:37:51

这里的主要混淆是假设所有的.NET库(在本例中是扩展的Numerics库,它不是BCL的一部分)都是用标准C#编写的。情况并不总是这样,不同的语言有不同的规则。

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

代码语言:javascript
复制
ldarg.0
ldarg.1
ceq
ret

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

即使这样,在编译C#代码时也不会使用实际的运算符-编译器会进行大量优化,就像本例中一样,它只用简单的ceq替换了op_Equality调用。同样,您不能在自己的DoubleEx结构中复制它-这是编译器的魔力。

这在.NET中肯定不是独有的情况--有很多代码是无效的、标准的C#。原因通常是(a)编译器黑客和(b)一种不同的语言,以及奇怪的(c)运行时黑客(我正在看着你,Nullable!)。

由于Roslyn C#编译器是oepn源代码,我实际上可以向您指出决定重载解决方案的位置:

The place where all binary operators are resolved

The "shortcuts" for intrinsic operators

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

票数 37
EN

Stack Overflow用户

发布于 2016-02-01 23:30:57

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

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

代码语言:javascript
复制
public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}

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

因此,您在这里看到的是运算符的相同情况。通常,如果您直接使用double类型,它将永远不会被调用。顺便说一句,它在CIL中的源代码如下:

代码语言:javascript
复制
.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原语类型来处理它。

票数 12
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35133777

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档