web-dev-qa-db-ja.com

Doubleの「==」演算子の定義

何らかの理由で、クラス Double の.NET Frameworkソースに忍び込んでいて、==は:

public static bool operator ==(Double left, Double right) {
    return left == right;
}

同じロジックがevery演算子にも適用されます。


  • そのような定義のポイントは何ですか?
  • どのように機能しますか?
  • 無限再帰を作成しないのはなぜですか?
125
Thomas Ayoub

実際には、コンパイラは==演算子をceq ILコードに変換すると、言及した演算子は呼び出されません。

ソースコード内の演算子の理由は、C#以外の言語から呼び出して、CEQ呼び出しに直接(またはリフレクションを介して)変換しないためです。コードwithin演算子willCEQにコンパイルされるため、無限再帰はありません。

実際、リフレクションを介して演算子を呼び出すと、(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 

興味深いことに、整数型には同じ参照演算子は(参照ソースにもリフレクションにも)存在せず、SingleDoubleDecimalStringのみが存在します。 、およびDateTimeは、他の言語から呼び出されるために存在するという私の理論を反証します。明らかに、これらの演算子なしで他の言語の2つの整数を同等にすることができるので、「なぜdoubleに存在するのか」という質問に戻ります。

62
D Stanley

ここでの主な混乱は、すべての.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#のプリミティブです。

37
Luaan

プリミティブ型のソースはわかりにくい場合があります。 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プリミティブ型として処理されます。

12
György Kőszeg

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
8
Daniel Pratt