2012-07-10 10 views
16

delegateキーワードはC#で使用する場合、C#コンパイラが自動的System.MulticastDelegateクラスから派生したクラスを生成が含ま生成しました。コンパイラは、代表キーワードの密封されたクラスは仮想メソッド

このコンパイラ生成されたクラスは、同様に3つのメソッドが含まれています:Invoke, BeginInvoke and EndInvoke

すべてのこれらの3つの方法がpublic virtual externをマークされているが、興味深いことに、クラス自体はsealedマークされます。密閉されたクラスで定義され

仮想メソッドは直感に反するよう打つが、C#で、実際に違法であるだけではなく。

だから私の質問は、このための具体的な理由がある場合、またはそれだけで心の中でいくつかの仮定の将来の拡張を維持行われ、それらの無害なものの一つですか?

編集1:

理由は対照的に、そのデリゲートオブジェクトは常に3のいずれかを実行しようとする前に、CLRによりnullをチェックされているので「と呼ぶ」から「callVirt」IL命令コードの使用を強制することができます方法?この点に関して私はdelegateが特別なケースであるべき理由を見逃していますが。

(それが微小であってもよいが)、

callvirtの使用を強制するために、パフォーマンスヒットしなかった編集2:

追加さCILタグ、それが委任を定義するC#の方法が判明として実際にはCIL標準によって義務づけられています。標準の状態(次は全文ではない)

代表者は基本型System.Delegateを持ちます。参加者は 密封さ宣言されなければならない、とここで指定したとしてデリゲートを持たなければならない唯一のメンバーは、最初の2つのまたはすべての4つの方法のいずれか です。これらの メソッドは、実行時に宣言され、管理されなければならない。その本体はVESによって自動的に作成されるため、本体には 本体はありません。デリゲートで利用可能なその他の メソッドは、基底クラスライブラリの System.Delegateクラスから継承されます。デリゲートメソッドは、次のとおり

  1. インスタンスコンストラクタ
  2. はInvokeメソッドは、仮想
  3. なければならない存在する場合EndInvokeをメソッドが仮想なければならない、BeginInvokeを法仮想
  4. なければなりません

これは間違いなくコンパイラプロセスの副作用ではなく、他の興味深いコンパイラ出力と同様です。

標準が何かを強調しているのなら、何らかの正当な理由と根拠が必要です。

ここで質問はなぜ、デリゲートのCIL標準が密封と仮想で同時に強調されるのでしょうか?その体は、VESによって自動的に作成されなければならないので、

は、ここでキャッチ嘘?:

をい彼らは、ボディを持っていてはなりません。

VES/CLR生成ボディがこれらのメソッドの呼び出し時に実行されるように仮想マークが付けられていますか?

+8

Cで有効でないものは、ILで完全に有効である可能性があることを忘れないでください。他のインスタンスが現在あります。これは別の可能性があります。しかし興味深いのは+1です。 –

+0

私はそれをILDasmで試して、overrideキーワードはILでvirtualに変換されています。 – MBen

+0

@MBenはい、C#で生成されたデリゲートクラスの場合、オーバーライドする対応する基本クラスのメソッドはありません。したがって、ここでの仮想キーワードは 'override'の結果ではありません:) –

答えて

3

私が疑問に思ったように、この密封されたバーチャルアノーマリは実際にはCIL標準によって義務づけられています。なぜCIL標準が、代理メソッドInvoke,BeginInvokeおよびEndInvokeが仮想でなければならないと同時に、Delegate継承クラスを封印することを義務付けているのかについてはっきりしない。

また、SSCLIコードを実行した後、JITコンパイラの内部最適化は、密封クラスの仮想メソッドの任意のcallvirt呼び出しを、追加のヌルチェックを伴う通常の呼び出しに自動的に変換することを学びました。これは、Invoke(または他の2つの)メソッドがcallvirt命令によって呼び出されたときに、デリゲートがパフォーマンスを損なうことはないことを意味します。

デリゲートの呼び出しが呼び出されると、CLRはILコードをコンパイルするのではなく、このメソッドのために高度に最適化されたボディを自動的に生成し、 '通常の'メソッドのために本体を生成します。これはILにvirtualというマークが付いていることとは関係ありません。

私は、ILコードを手作業で修正し、生成されたデリゲートクラスのILコードから仮想を安全に削除できることも確認しました。生成されたアセンブリは、CIL標準に違反しているにもかかわらず、完全に正常に動作します。

.class private auto ansi beforefieldinit MainApp 
     extends [mscorlib]System.Object 
{ 
    .class auto ansi sealed nested private Echo 
     extends [mscorlib]System.MulticastDelegate 
    { 
    .method public hidebysig specialname rtspecialname 
      instance void .ctor(object 'object', 
           native int 'method') runtime managed 
    { 
    } // end of method Echo::.ctor 

    .method public hidebysig instance int32 Invoke(int32 i) runtime managed 
    { 
    } // end of method Echo::Invoke 

    .method public hidebysig instance class [mscorlib]System.IAsyncResult 
      BeginInvoke(int32 i, 
         class [mscorlib]System.AsyncCallback callback, 
         object 'object') runtime managed 
    { 
    } // end of method Echo::BeginInvoke 

    .method public hidebysig instance int32 EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed 
    { 
    } // end of method Echo::EndInvoke 

    } // end of class Echo 

    .method public hidebysig static void Main() cil managed 
    { 
    .entrypoint 
    // Code size  34 (0x22) 
    .maxstack 3 
    .locals init ([0] class MainApp app, 
      [1] class MainApp/Echo dele) 
    IL_0000: nop 
    IL_0001: newobj  instance void MainApp::.ctor() 
    IL_0006: stloc.0 
    IL_0007: ldloc.0 
    IL_0008: ldftn  instance int32 MainApp::DoEcho(int32) 
    IL_000e: newobj  instance void MainApp/Echo::.ctor(object, 
                  native int) 
    IL_0013: stloc.1 
    IL_0014: ldloc.1 
    IL_0015: ldc.i4.5 
    //callvirt can also be replaced by call without affecting functionality 
    // since delegate object is essentially not null here 
    IL_0016: callvirt instance int32 MainApp/Echo::Invoke(int32) 
    IL_001b: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0020: nop 
    IL_0021: ret 
    } // end of method MainApp::Main 

    .method private hidebysig instance int32 
      DoEcho(int32 i) cil managed 
    { 
    // Code size  7 (0x7) 
    .maxstack 1 
    .locals init ([0] int32 CS$1$0000) 
    IL_0000: nop 
    IL_0001: ldarg.1 
    IL_0002: stloc.0 
    IL_0003: br.s  IL_0005 

    IL_0005: ldloc.0 
    IL_0006: ret 
    } // end of method MainApp::DoEcho 

    .method public hidebysig specialname rtspecialname 
      instance void .ctor() cil managed 
    { 
    // Code size  7 (0x7) 
    .maxstack 8 
    IL_0000: ldarg.0 
    IL_0001: call  instance void [mscorlib]System.Object::.ctor() 
    IL_0006: ret 
    } // end of method MainApp::.ctor 

} // end of class MainApp 

私は仮想メソッドを通常のインスタンスメソッドに変換しました。

この変更されたILは完全にうまく動作するので、封印されたデリゲートクラスの標準的な必須仮想メソッドは必要でないことが証明されます。通常のインスタンスメソッドでもかまいません。

この異常は、これら3つのデリゲートメソッドを呼び出すことが実際には他のメソッド(つまり、通常の仮想メソッドと同じような実行時の多型性)を呼び出す結果になることを強調するか、そうであった代理人に関連するいくつかの将来的な仮説の強化に対応する。

+1

ILをコンパイルすることとは反対の、高度に最適化されたボディについて知ったところに私を紹介することはできますか? –

3

これは、コンパイルプロセスの副作用です。私はこれの正確な理由を知らない、この種の行動の例がさらにある。たとえば、コンパイルされた静的クラスは抽象クラスであり(したがって、インスタンスを作成することはできず、継承することもできません)。

+0

Staticクラスについての非常に興味深い点:)...しかし、私は抽象的なシールはかなり静的クラスのセマンティクスがMSILで表現できる唯一の方法だと思いますそれをインスタンス化することはできません。それを継承することはできません。クラス自体にはインスタンスメンバーが含まれていません。静的な契約です。 –

+0

質問に私のedit2を見てください。 –

1

デリゲートに固有のようではありません。 私はこの例を試し:

public abstract class Base 
    { 
     public abstract void Test(); 
    } 

    public sealed class Derived : Base 
    { 
     public override void Test() 
     { 
      throw new NotImplementedException(); 
     } 
    } 

とILDASMにおいてI)は(試験の実施のためにこれを得る:

.method public hidebysig virtual instance void 
     Test() cil managed 
{ 
    // Code size  7 (0x7) 
    .maxstack 8 
    IL_0000: nop 
    IL_0001: newobj  instance void [mscorlib]System.NotImplementedException::.ctor() 
    IL_0006: throw 
} // end of method Derived::Test 

はoverrideキーワードがCLRキーワードではないことがございます。

+1

お返事ありがとうございます。これは別の非常に興味深い発見ですが、デリゲートとの違いは、私が言及した3つのメソッドは、 '新しく'生成されたメソッドであり、オーバーライドではないということです。上書きは本質的に仮想であり、私が言及したように、MSILには対応する上書きキーワードがないとも考えています。 –

+0

良い点があります。 – MBen

+0

ちょうど私が学んだちょっとしたポイント。バーチャルメソッドがオーバーライドであることを意味する前にキーワード 'newslot'の不在... –

4

タイプ定義を見ていた逆アセンブラがうまくいっています。これはILをC#のような認識可能な言語に翻訳する必要があります。これは一般的に完全な忠実度で行うことはできませんが、ILの規則はではなく、はC#言語規則と同じです。これはデリゲートでのみ起こるのではなく、C#コードで仮想宣言していないにもかかわらず、インターフェイスの実装方法も仮想です。

ILは、コード解析からターゲットオブジェクトを判断できる場合、実際にはコンパイラが仮想メソッドの非仮想呼び出しを発行できるようにします。しかし、それはデリゲートやインターフェイス呼び出しでは決して起こりません。また、ILは非仮想メソッドへの仮想呼び出しを可能にします。これは、C#コンパイラがgustoを使ってインスタンスメソッドをnull と呼ぶことができないという保証を実装するものです。

しかし、C#の使用法は、CLRが設計された後にのみ発見される巧妙なトリックです。 仮想の当初の意図は、メソッドがCallvirtで呼び出されるべきであることを注釈することでした。最終的には、コンパイラがデリゲートとインターフェイスの動作を認識しており、常にCallvirtを送出するため、問題はありません。実際のメソッド呼び出しは、Callvirtのアクティブ化を前提としたCLRコードで実装されています。

+0

ありがとうございます。質問に私のedit2を見てください。 –

関連する問題