2012-01-20 13 views
14

this questionから簡体字および可能性を処分したLinqPad(なしoffsensive)、このような単純なコンソールアプリケーションからの影響を与える:暗黙のメソッドグループの変換落とし穴(その2)

public class Program 
{ 
    static void M() { }  
    static void Main(string[] args) 
    { 
     Action a = new Action(M); 
     Delegate b = new Action(M); 
     Console.WriteLine(a == b);  //got False here 
     Console.Read(); 
    }   
} 

オペレータからの「偽」の結果上記のコードのCILでceq(詳細は元の質問をご覧ください)。だから私の質問は:

なぜ==call Delegate Equalsの代わりにceqに翻訳されているのですか?

ここでは、デリゲートとアクションの間の(un)ラッピングについては気にしません。最後に、a == bを評価するとき、aはタイプActionであり、bはDelegateである。スペックから:

7.3.4バイナリ演算子オーバーロード解決

opがオーバーロードバイナリ演算子であるフォームXオペアンプY、動作、Xは、X型の発現 であり、そしてyは

•オペレータop(x、y)の のXとYによって提供される候補ユーザー定義演算子のセットが決定されます。集合は、Xによって提供される候補演算子の と、Yによって提供される候補演算子 とから成り、それぞれが§7.3.5の規則を使用して決定される。 XとYが同じタイプの場合、またはXとYが共通の ベースタイプから派生した場合、共有候補演算子は の組み合わせでのみ発生します。

•候補ユーザー定義演算子のセットが でない場合、これは 操作の候補演算子のセットになります。そうでない場合、既定の2項演算子op の実装は、持ち上げられたフォームを含めて、操作の候補演算子の集合 になります。与えられた演算子の定義済みの実装 は、演算子 (§7.8から§7.12)の記述で指定されています。 は、引数リスト(X、Y)に対する最良オペレータ を選択するための候補オペレータのセットに適用され、このオペレータは の結果となりさ§7.5.3のオーバーロード解決規則•

過負荷解決プロセス。オーバーロードの解決方法 が1つの最適な演算子を選択できなかった場合、バインディング時エラーが発生します。型TおよびOPがオーバーロード演算子であり、Aは、引数リスト、候補ユーザ定義演算子のセットである操作オペレータOP(A)、与えられ

7.3.5候補ユーザ定義演算子

オペレータop(A)の によって提供されるTは、次のように決定されます。

•タイプ T0を決定します。 TがNULL可能タイプである場合、少なくとも一人のオペレータが適用される場合に、T0は、そうでなければT0 はT. T0内のすべてのオペレータOPの宣言と、そのような演算子のすべての持ち上げ 形態について

•に等しく、その基礎となるタイプである (7.5.3。1)の場合、 の候補演算子の集合は、T0の該当するすべての演算子で構成されます。

•T0がオブジェクトの場合、候補演算子のセットは空です。 T0は、型パラメータである場合

•そうでない場合は、T0が提供する候補オペレータのセットは、T0の直接基底クラス、またはT0の 有効な基底クラスによって提供される候補の演算子のセット あります。スペックから

、a及びbは、同じ基本クラスDelegateを有する明らかDelegateで定義されたオペレータルール==は(オペレータ==は、本質的にDelegate.Equalsを呼び出す)ここで適用されるべきです。しかし、ユーザー定義演算子の候補リストが空で最後にObject ==が適用されたように見えます。

(2)FCLコードはC#言語仕様に従うべきですか?いいえ、何か特別な扱いがあるので、私の最初の質問は無意味です。そして、私たちはこれらの質問に「オハイオ州、FCLの特別な扱いです。彼らはできないことをすることができます」と答えています。スペックは外部のプログラマ向けです。

+0

そのため、値型のセマンティクスが必要なときには、 'Equals'を使用するのが最善です。 (潜在的に)壊れた演算子のオーバーロードのため。 – Groo

+0

@Groo:まったく。とにかく、私は、 '意図していない参照比較の可能性があります。値の比較を行うには、右手側に「System.Action」と入力します。 –

+0

ユーザ定義のクラス階層で同じことをしようとすると、カスタム '=='メソッドが呼び出されます。 – AakashM

答えて

4

オペレータには、ユーザー定義の演算子と事前定義の演算子の2種類があります。セクション7.3.5「候補ユーザー定義演算子」は、事前定義された演算子には適用されません。 たとえば、decimalの演算子は、逆コンパイラでユーザー定義演算子のように見えますが、C#はあらかじめ定義された演算子として扱い、数値昇格を適用します(数値昇格はユーザー定義演算子には適用されません)。

第7.10.8項「委任等価演算子」は定義済み演算子としてoperator ==(Delegate, Delegate)を定義しているため、ユーザー定義演算子に関するすべてのルールはこの演算子には適用されないと思います(100%クリアではありませんこの場合のように仕様では、事前定義された演算子は、ユーザー定義演算子の場合は適用されません)。

Every delegate type implicitly provides the following predefined comparison operators: 
bool operator ==(System.Delegate x, System.Delegate y); 
bool operator !=(System.Delegate x, System.Delegate y); 

しかしSystem.Delegate自体はデリゲート型と考えられ、そのオーバーロードの解決のための唯一の候補者はoperator ==(object, object)ですされていません。

+1

次に、Console.WriteLine((Delegate)a == b); 'に変更すると、出力が' True'に変わるのはなぜですか?コンパイル時の型のオペランドも "デリゲート型"です(非具体クラス 'System.Delegate'は"デリゲート型 "ではありません)ので、依然としてデリゲート型のオーバーロードが使用されます。 (私はこのスレッドを見つけました。自分自身の質問[演算子のオーバーロードの解決==汎用バリアントのタイプ](http://stackoverflow.com/questions/22408165/)がこれにリンクされています) –

-1

ここで重要なのは、==タイプとDelegateタイプのEqualsメソッドが2つの異なることです。参照型の場合、==演算子がオーバーライドされない限り、参照先が同じオブジェクトを指しているかどうかを調べます(== Operator (C#)参照)。

ので==この場合ReferenceEqualsであり、あなたはそれらが内部で同じメソッドを呼び出していても、二つの異なるActionオブジェクトを作成しているので、メモリ内の異なる場所に異なるオブジェクトであり、値またはstringタイプではありません2つのオブジェクトが同じことをするかどうかを確認するためにオーバーライドされたDelegate.Equalsメソッドを呼び出しません。 string以外の参照タイプの場合、これはデフォルト動作の==またはEqualsです。

+1

参照型の場合、 '=='演算子はそれが何であれその特定のタイプに対して行うこと。多くのタイプでは、参照平等チェックは実行されません。 –

+0

私は '= ""演算子が私のステートメントにオーバーライドされていない限り、'句を追加しました。キーは、 "==" opは一般的に参照を比較するために使用され、Equalsメソッドは一般的に値を比較するために使用されるということです。 – SamuelWarren

+1

@highphilospher:私はdownvoteを元に戻していますが、C#は、参照平等のために使われる '=='というJavaの考え方を共有していません。ほとんどはそうではありません。参照の平等が必要な場合は、 'object.ReferenceEquals'静的メソッドを使用してください。 –

5

コンパイラは、代理人と非常に異なり、珍しいです。暗黙の処理がたくさんあります。 このガイドの「共通基本タイプ」ルールは、「ユーザー定義演算子」に適用されます。デリゲートは内部およびシステムです。たとえば、Action a = new Action(M);の代わりにAction a = M;と書くことができます。その後、a += M;を追加することができます。 CILで何が起きているかチェックしてください。初めて面白いです。

さらに詳しく:代議員を比較することは危険であり、自明ではありません。すべてのデリゲートは実際にマルチキャストデリゲートです。同じデリゲートに複数の関数ポインタを追加できます。デリゲート[L(); M(); N();]はデリゲート[M();]と同じですか?関数ポインタには、クラスインスタンス(メソッドなど)が含まれます。 は[b.M();]に等しいですか?ケースに依存するすべて、比較の実装では呼び出しリストをステップ実行する必要があります。

共通基底​​型からの継承継承は暗黙的です。この問題は別のシナリオで直面する可能性があります。ジェネリック制約:ジェネリックパラメータTに対する制約として委任を指定することはできません。コンパイラはこれを明示的に拒否します。 Delegateから継承した独自のクラスの作成についても同じです。

これは両方の質問に対する回答です。「Delegate」は純粋にFCLではなく、コンパイラと密接に結びついています。Equals(a, b)

+0

これは言語仕様に関する質問であり、私は "これはコンパイラの魔法です"とは思っていません。 – Daniel

+0

はい、マルチキャストの代理人を比較するのは簡単なことではありませんが、C#仕様ではどのように動作するか明確に定義されています。 – Daniel

3

警告CS0253:意図しない参照比較。値の比較を行うには、右側に「System.Action」と入力してください。

これは、そのC#コードで得られる警告です。この警告を無視しないで、C#チームは、この比較のために生成したコードが予期せぬものであることをよく知っていました。彼らはというコードを生成していないので、あなたが期待したことを簡単に達成できました。このコードのように行います。

ほぼ同じMSILを生成
Module Module1 
    Sub M() 
    End Sub 

    Sub Main() 
     Dim a = New Action(AddressOf M) 
     Dim b = DirectCast(New Action(AddressOf M), [Delegate]) 
     Console.WriteLine(a = b)  ''got True here 
     Console.Read() 
    End Sub 
End Module 

、代わりにceqのことを除いて、あなたが得る:あなたがC#のコードが何を望んだものを

IL_001d: call bool [mscorlib]System.Delegate::op_Equality(class [mscorlib]System.Delegate, 
                  class [mscorlib]System.Delegate) 

。あなたがそれを認識しなかった場合に備えて、それはVB.NETコードでした。そうでなければ、マイクロソフトでは2つの主要な管理言語を保持しています。しかし、非常に異なるユーザビリティの選択肢があります。コードを生成する方法が複数ある場合は常に、C#チームはのパフォーマンスを一貫して選択しました。のVBのチームは常にです。

パフォーマンスは確かに重要です。デリゲートオブジェクトを比較すると、は高価なです。規則はEcma-335、セクションII.14.6.1に記載されています。しかし、あなたはそれを自分のために推論することができます、多くの点検があります。デリゲートターゲットオブジェクトが互換性があるかどうかをチェックする必要があります。また、各引数について、値が変換可能かどうかをチェックする必要があります。 C#チームが非表示にしたくない費用。

あなたは直感的な選択をしたことを知らせる警告が表示されます。 。

+0

最も驚くべきことC#では(アクション)a ==(アクション)b'と((デリゲート)a ==(デリゲート)b'の両方がデリゲート値の比較になり、 '(アクション)a ==(デリゲート)b '参照平等チェック(コンパイル時の警告付き)に進みます。これは不自然です。特に、 'Action'が' Delegate'に暗黙的に(参照変換によって)変換可能であるためです。 –

+0

あなたは答えの半分を得ています(これは良いことです)。しかし、私はまだC#コンパイラがC#言語仕様に合致していないことを容易に認識していると感じています。また、リンクされたスレッドを参照してください_演算子 '=='上のオーバーロード解消、ジェネリックジェネリックデリゲートの型_。しかし、私はマルチキャスト代理人を比較することはコストがかかり、参照平等をチェックすることは非常に安価で、C#とVB.NETでは "最適化"するための考え方が異なることに同意します。 –