Double的“==”运算符的定义
出于某种原因,我偷偷进入Double
类的.NET Framework源代码,发现==
的声明是:
public static bool operator ==(Double left, Double right) { return left == right; }
同样的逻辑适用于每个操作员。
- 这个定义是什么意思?
- 它是如何工作的?
- 为什么不创build一个无限recursion?
实际上,编译器会将==
运算符转换为一个ceq
IL代码,并且您提到的运算符将不会被调用。
源代码中操作符的原因很可能是因为可以从C#以外的其他语言中直接调用(或通过reflection)将其转换为CEQ
调用。 运算符中的代码将被编译成一个CEQ
,所以没有无限recursion。
事实上,如果通过reflection调用操作符,则可以看到调用操作符(而不是CEQ
指令),显然不是无限recursion(因为程序按预期终止):
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
有趣的是,相同的操作符不存在(在参考源或通过reflection)整数types,只有Single
, Double
, Decimal
, String
和DateTime
,这反驳了我的理论,他们存在从其他语言调用。 显然,如果没有这些操作符,就可以将其他语言中的两个整数等同起来,所以我们回到了“为什么它们存在double
”这个问题上?
这里主要的困惑是,你假定所有的.NET库(在这个例子中,扩展的数字库不是 BCL的一部分)是用标准的C#编写的。 情况并非总是如此,不同的语言有不同的规则。
在标准C#中,由于运算符重载parsing的方式,你看到的代码会导致堆栈溢出。 但是,代码实际上并不是标准的C# – 它基本上使用C#编译器的未公开的function。 它不是调用操作符,而是发出以下代码:
ldarg.0 ldarg.1 ceq ret
就是这样:)没有100%的等价的C#代码 – 这在C#中是不可能的, 你自己的types。
即使这样,在编译C#代码时也没有使用实际的操作符 – 编译器会进行一些优化,就像在这种情况下,它只用简单的ceq
来代替op_Equality
调用。 再一次,你不能在你自己的DoubleEx
结构中复制这个 – 这是编译器的魔力。
这当然不是在.NET中的独特情况 – 有很多代码是无效的,标准的C#。 原因通常是(a)编译器黑客和(b)不同的语言,奇怪的(c)运行时间黑客(我看着你, Nullable
!)。
由于Roslyn C#编译器是oepn源代码,所以我可以在重载决议决定的地方指向你:
所有二元运算符都解决的地方
内在运营商的“捷径”
当您查看快捷方式时,您会看到内部double运算符的double和double结果之间的等价关系,而不是该types上定义的实际==
运算符。 .NETtypes系统必须假装Double
是一个类似于其他types的types,但是C#没有 – 在C#中double
是一个原语。
原始types的来源可能会令人困惑。 你看过Double
结构的第一行吗?
通常你不能像这样定义recursion结构:
public struct Double : IComparable, IFormattable, IConvertible , IComparable<Double>, IEquatable<Double> { internal double m_value; // Self-recursion with endless loop? // ... }
原始types在CIL中也有其本地支持。 通常他们不被视为面向对象的types。 如果在CIL中用作float64
,那么double就是一个64位的值。 但是,如果它是作为一个通常的.NETtypes处理,它包含一个实际的值,它包含任何其他types的方法。
所以你在这里看到的是对于运营商来说同样的情况。 通常情况下,如果直接使用doubletypes,它将永远不会被调用。 顺便说一句,它的源代码在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
基本types来处理。
我用JustDecompile看了一下CIL 。 内部==
被转换为CIL ceq操作码。 换句话说,这是原始的CLR平等。
我很好奇,当比较两个double值时,C#编译器是否会引用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
如System.Runtime.Versioning命名空间的Microsoft文档中所述:此命名空间中的types适用于.NET Framework,不适用于用户应用程序。System.Runtime.Versioning命名空间包含支持版本控制的高级types.NET框架的并行实现。