何らかの理由で、クラス Double
の.NET Frameworkソースに忍び込んでいて、==
は:
public static bool operator ==(Double left, Double right) {
return left == right;
}
同じロジックがevery演算子にも適用されます。
実際には、コンパイラは==
演算子をceq
ILコードに変換すると、言及した演算子は呼び出されません。
ソースコード内の演算子の理由は、C#以外の言語から呼び出して、CEQ
呼び出しに直接(またはリフレクションを介して)変換しないためです。コードwithin演算子willは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
は、他の言語から呼び出されるために存在するという私の理論を反証します。明らかに、これらの演算子なしで他の言語の2つの整数を同等にすることができるので、「なぜdouble
に存在するのか」という質問に戻ります。
ここでの主な混乱は、すべての.NETライブラリ(この場合、notはBCLの一部である拡張数値ライブラリ)が標準C#で書かれていると仮定していることです。これは常に当てはまるわけではなく、言語によってルールが異なります。
標準のC#では、演算子のオーバーロード解決が機能するため、表示されているコードがスタックオーバーフローになります。ただし、コードは実際には標準のC#ではありません。基本的に、ドキュメント化されていないC#コンパイラの機能を使用します。演算子を呼び出す代わりに、次のコードを発行します。
ldarg.0
ldarg.1
ceq
ret
それだけです:) 100%同等のC#コードはありません-これは、C#ではyour ownタイプでは不可能です。
それでも、C#コードのコンパイル時には実際の演算子は使用されません。コンパイラは、この場合のように、op_Equality
呼び出しを単純なceq
に置き換える多くの最適化を行います。繰り返しますが、これを独自のDoubleEx
構造体で複製することはできません。これはコンパイラの魔法です。
これは確かに.NETのユニークな状況ではありません-有効ではない多くのコードが標準C#にあります。理由は通常、(a)コンパイラハックと(b)別の言語で、奇妙な(c)ランタイムハック(私はあなたを見ている、Nullable
!)です。
Roslyn C#コンパイラはoepnソースであるため、実際にオーバーロード解決が決定される場所を指摘できます。
ショートカットを見ると、doubleとdoubleが等しいと、組み込みのdouble演算子、つまり、型に定義されている実際の==
演算子でneverが得られることがわかります。 .NET型システムは、Double
が他の型のように振る舞う必要がありますが、C#はそうではありません-double
はC#のプリミティブです。
プリミティブ型のソースはわかりにくい場合があります。 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
}
ご覧のとおり、無限ループはありません(System.Double::op_Equality
を呼び出す代わりにceq
インストゥルメントが使用されます)。そのため、doubleがオブジェクトのように扱われると、operatorメソッドが呼び出され、最終的にCILレベルでfloat64
プリミティブ型として処理されます。
JustDecompileで [〜#〜] cil [〜#〜] を見てみました。内部の==
は、CIL ceq opコードに変換されます。言い換えれば、それは原始的なCLRの平等です。
2つの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