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


126

なんらかの理由で、クラスの.NET Frameworkソースに潜入しDoubleていて、の宣言==が次のとおりであることがわかりました。

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

同じロジックがすべてのオペレーターに適用されます


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

17
無限の再帰を期待しています。
HimBromBeere

5
doubleとの比較には使用されずceq、ILで発行されると確信しています。これは、ドキュメントの目的を満たすためだけにありますが、ソースを見つけることができません。
2016

2
ほとんどの場合、この演算子はリフレクションを通じて取得できます。
Damien_The_Unbeliever 2016

3
これは決して呼び出されません。コンパイラーは(ceq opcode)で実行された等価ロジックを持っています。Double の==演算子が呼び出されるのはいつですか?を
Alex K.

1
@ZoharPeled doubleをゼロで除算することは有効であり、正または負の無限大になります。
Magnus

回答:


62

実際には、コンパイラーは==演算子をceqILコードに変換し、指定した演算子は呼び出されません。

ソースコードでの演算子の理由は、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 

興味深いことに-同じ演算子はのみ、整数型のために(参照元や反射によってどちらか)が存在しないSingleDoubleDecimalString、そしてDateTime、彼らは他の言語から呼ばれるように存在していることが私の理論を反証います。明らかに、これらの演算子なしで他の言語の2つの整数を同等と見なすことができるので、「なぜそれらが存在するのか」という質問に戻りdoubleます。


12
これで私が目にすることができる唯一の問題は、C#言語仕様では、オーバーロードされた演算子が組み込み演算子よりも優先されるということです。したがって、準拠するC#コンパイラは、オーバーロードされた演算子がここで使用できることを確認し、無限再帰を生成するはずです。うーん。厄介。
Damien_The_Unbeliever 2016

5
それは質問に答えません、imho。それはコードが何に翻訳されるかを説明するだけで、理由は説明しません。セクション7.3.4によると、C#言語仕様の2項演算子のオーバーロードの解決により、無限再帰も期待されます。参照ソース(referencesource.microsoft.com/#mscorlib/system/…)は、ここでは実際には適用されないと思います。
Dirk Vollmar、2016

6
@DStanley-私は生産されたものを否定していません。私はそれを言語仕様と一致させることができないと言っています。それが厄介なことです。私はRoslynを熟読し、特別な処理をここで見つけることができるかどうかを確認しようとしていましたが、現時点ではこれを行う準備ができていません(間違ったマシン)
Damien_The_Unbeliever

1
@Damien_The_Unbelieverだからこそ、仕様の例外か、「組み込み」演算子の別の解釈だと思います。
Dスタンリー

1
@Jon Skeetはまだ回答もコメントもしていないので、バグ(つまり、仕様違反)だと思います。
TheBlastOne 2016

37

ここでの主な混乱は、すべての.NETライブラリ(この場合、BCLの一部ではない拡張数値ライブラリ)が標準のC#で記述されていると想定していることです。これは常に当てはまるわけではなく、言語によってルールも異なります。

標準のC#では、演算子のオーバーロードの解決方法が原因で、表示されているコードがスタックオーバーフローを引き起こします。ただし、コードは実際には標準のC#にはありません。基本的に、ドキュメントに記載されていないC#コンパイラの機能を使用しています。オペレーターを呼び出す代わりに、次のコードを発行します。

ldarg.0
ldarg.1
ceq
ret

それだけです:) 100%に相当するC#コードはありません。これは、C#では独自の型では不可能です。

それでも、C#コードのコンパイル時には実際の演算子は使用されません。この場合のように、コンパイラーは一連の最適化を行い、op_Equality呼び出しを単純なに置き換えceqます。繰り返しますが、これを独自のDoubleEx構造体に複製することはできません。コンパイラの魔法です。

これは確かに.NETに固有の状況ではありません。有効ではない標準C#のコードがたくさんあります。その理由は通常、(a)コンパイラのハックと(b)奇妙な(c)ランタイムのハック(私はあなたを見ているNullable!)

Roslyn C#コンパイラはoepnソースなので、オーバーロードの解決が決定される場所を実際に示すことができます。

すべての二項演算子が解決される場所

組み込み演算子の「ショートカット」

ショートカットを見ると、doubleとdoubleの等価性が、型に定義された実際の演算子ではなく、組み込みのdouble演算子であることがわかります==。.NET型システムはDouble、他の型と同様に型を装う必要がありますが、C#にはありませんdouble-C#のプリミティブです。


1
参照ソースのコードが単に「リバースエンジニアリング」されていることに同意するかどうかはわかりません。コードには、コンパイラディレクティブ(#ifs)およびコンパイル済みコードには存在しない他のアーティファクトがあります。それが逆のために設計された場合はプラスdouble、なぜそれが逆のために設計されていませんintlong?私はソースコードに理由があると思いますが==、演算子の中での使用はCEQ再帰を防ぐためにコンパイルされると信じています。演算子はそのタイプの「事前定義された」演算子であるため(オーバーライドすることはできません)、オーバーロードルールは適用されません。
Dスタンリー

@DStanley すべてのコードがリバースエンジニアリングされていることをほのめかしたくありませんでした。繰り返しになりますが、これdoubleはBCLの一部ではありません。別のライブラリにあり、たまたまC#仕様に含まれているだけです。はい、==はにコンパイルされceqますが、これは、独自のコードで複製できないコンパイラハックであり、C#仕様の一部ではない(構造体のfloat64フィールドと同様Double)ことを意味します。これはC#の契約部分ではないため、C#コンパイラでコンパイルされたとしても、有効なC#として扱う意味はほとんどありません。
Luaan 16

@DStanely実際のフレームワークの構成を見つけることができませんでしたが、.NET 2.0のリファレンス実装では、トリッキーな部分はすべてC ++で実装されたコンパイラ組み込み関数です。もちろん、.NETネイティブコードはまだたくさんありますが、「2つのdoubleを比較する」などは、純粋な.NETでは実際にはうまく機能しません。これが、浮動小数点数がBCLに含まれていない理由の1つです。とは言って、コードは(非標準)C#に実装されています。おそらく先に述べた理由によります。他の.NETコンパイラがこれらの型を実際の.NET型として扱えるようにするためです。
Luaan 2016

@DStanleyしかし、大丈夫、ポイントを取る。「リバースエンジニアリングされた」参照を削除し、C#だけでなく、「標準のC#」に明示的に言及するように回答を書き換えました。そしてdouble、同じように扱わないでくださいintしてlong- intそしてlongそのプリミティブ型であるすべての.NET言語をサポートしている必要があります。float、そうdecimaldoubleはありません。
Luaan 2016

12

プリミティブ型のソースは混乱する可能性があります。Double構造体の最初の行を見ましたか?

通常、次のような再帰的な構造体は定義できません。

public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}

プリミティブ型は、CILでもネイティブサポートされています。通常、それらはオブジェクト指向型のようには扱われません。float64CILのように使用される場合、doubleは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メソッドが呼び出され、最終的float64にはCILレベルのプリミティブ型として処理されます。


1
この投稿の最初の部分を理解していない場合は(通常、独自の値タイプを記述しないため)、コードを試してくださいpublic struct MyNumber { internal MyNumber m_value; }。もちろん、コンパイルすることはできません。エラーはエラーCS0523:タイプ 'MyNumber'の構造体メンバー 'MyNumber.m_value'により構造体レイアウトに循環が発生します
Jeppe Stig Nielsen

8

私は見ましたCIL JustDecompileとします。内部==はCIL ceq opコードに変換されます。言い換えれば、それは原始的なCLRの同等性です。

C#コンパイラが参照するのceqか、それとも==2つのdouble値を比較するときに演算子演算子。私が思いついた簡単な例(以下)では、を使用しました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

-2

System.Runtime.Versioning名前空間に関するMicrosoftのドキュメントに示されているように、この名前空間にある型は、.NET Framework内で使用するためのものであり、ユーザーアプリケーション用ではありません。System.Runtime.Versioning名前空間には、 .NET Frameworkの並列実装。


System.Runtime.Versioningと関係がありSystem.Doubleますか?
Koopakiller 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.