由于某种原因,我偷偷摸摸该类的.NET Framework源代码Double
,发现其中的声明==
为:
public static bool operator ==(Double left, Double right) {
return left == right;
}
相同的逻辑适用于每个操作员。
- 这样定义的意义何在?
- 它是如何工作的?
- 为什么不创建无限递归?
ceq
在IL中发布。只是为了满足某些文档目的,尽管找不到源。
由于某种原因,我偷偷摸摸该类的.NET Framework源代码Double
,发现其中的声明==
为:
public static bool operator ==(Double left, Double right) {
return left == right;
}
相同的逻辑适用于每个操作员。
ceq
在IL中发布。只是为了满足某些文档目的,尽管找不到源。
Answers:
实际上,编译器会将==
运算符转换为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
”的问题?
这里的主要困惑是,您假设所有.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#中的原始类型。
#if
和其他不会在已编译代码中出现的构件。另外,如果它被反向工程为double
那么为什么不是反向工程的int
还是long
?我确实认为源代码是有原因的,但我认为==
将操作符内部的用法编译为a CEQ
可以防止递归。由于该运算符是该类型的“预定义”运算符(并且不能被覆盖),因此不适用重载规则。
double
它不是BCL的一部分-它在一个单独的库中,恰好包含在C#规范中。是的,将==
gets编译为ceq
,但这仍然意味着这是您无法在自己的代码中复制的编译器技巧,并且它不是C#规范的一部分(就像struct 的float64
字段一样Double
)。它不是C#的约定部分,因此即使将其用C#编译器进行编译,也没有什么意义。
double
以int
与long
- 相同的方式对待,int
并且long
它们是所有 .NET语言都必须支持的原始类型。float
,decimal
而double
不是。
基本类型的来源可能令人困惑。您是否看到过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方法,该方法最终将其作为float64
CIL级别上的原始类型进行处理。
public struct MyNumber { internal MyNumber m_value; }
。当然,它不能被编译。错误为错误CS0523:类型为'MyNumber'的结构成员'MyNumber.m_value'导致结构布局中出现循环
我看了一下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
如Microsoft文档对System.Runtime.Versioning命名空间的指示:在此命名空间中找到的类型旨在在.NET Framework中使用,而不用于用户应用程序。System.Runtime.Versioning命名空间包含支持以下版本控制的高级类型。 .NET Framework的并行实现。
System.Runtime.Versioning
什么关系System.Double
?