出于某种原因,我偷偷进入Double
类的.NET框架源代码,发现==
的声明是:
public static bool operator ==(Double left, Double right) {
return left == right;
}
同样的逻辑也适用于每个运算符。
发布于 2016-02-01 23:22:30
实际上,编译器会将==
操作符转换为ceq
IL代码,并且不会调用您提到的操作符。
在源代码中使用运算符的原因可能是,它可以从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
有趣的是-整型类型不存在相同的运算符(无论是在参考源中还是通过反射),只有Single
、Double
、Decimal
、String
和DateTime
,这反驳了我的理论,即它们存在是为了从其他语言调用。显然,在没有这些运算符的情况下,您可以在其他语言中将两个整数等同起来,因此我们又回到了“为什么它们存在于double
”的问题上。
发布于 2016-02-02 00:37:51
这里的主要混淆是假设所有的.NET库(在本例中是扩展的Numerics库,它不是BCL的一部分)都是用标准C#编写的。情况并不总是这样,不同的语言有不同的规则。
在标准C#中,由于操作符重载解析的工作方式,您所看到的代码片段将导致堆栈溢出。但是,代码实际上并不是标准的C# --它基本上使用了C#编译器中未记录的特性。它不是调用操作符,而是发出以下代码:
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#中的一个原语。
发布于 2016-02-01 23:30:57
原始类型的来源可能会令人困惑。您看过Double
结构的第一行吗?
通常你不能像这样定义一个递归结构:
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中的源代码如下:
.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
原语类型来处理它。
https://stackoverflow.com/questions/35133777
复制相似问题