18

キーワードがC#で使用されている場合delegate、C#コンパイラはクラスから派生したクラスを自動的に生成しSystem.MulticastDelegateます。

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

これら3つのメソッドはすべてマークされていますpublic virtual externが、興味深いことに、クラス自体がマークされていsealedます。

封印されたクラスで定義された仮想メソッドは、直感に反するだけでなく、C#では実際には違法です。

だから私の質問は、これには特定の理由がありますか、それともいくつかの仮想的な将来の強化を念頭に置いて行われた無害なことの1つにすぎませんか?

編集1:

その理由は、3つのメソッドのいずれかを実行しようとする前にデリゲートオブジェクトがCLRによって常にnullであるかどうかがチェックされるように、「call」ではなく「callVirt」ILオペコードの使用を強制するためでしょうか。delegateなぜこの点で特別な場合なのかはわかりませんが。

また、使用を強制することはパフォーマンスの打撃ではありませんcallvirt(それはごくわずかかもしれませんが)

編集2:

デリゲートを定義するC#の方法が実際にはCIL標準によって義務付けられていることが判明したため、CILタグを追加しました。標準では、(以下は全文ではありません)と述べています

デリゲートは、基本タイプがSystem.Delegateである必要があります。代議員は封印されていると宣言され、代議員が持つべき唯一のメンバーは、ここで指定されている最初の2つまたは4つすべての方法のいずれかです。これらのメソッドは、実行時に宣言され、管理されます。ボディはVESによって自動的に作成されるため、ボディを持たないものとします。デリゲートで使用可能な他のメソッドは、基本クラスライブラリのクラスSystem.Delegateから継承されます。デリゲートメソッドは次のとおりです。

  1. インスタンスコンストラクタ
  2. Invokeメソッドは仮想である必要があります
  3. BeginInvokeメソッドは、存在する場合、仮想である必要があります
  4. EndInvokeメソッドは仮想である必要があります

したがって、これはコンパイラプロセスの副作用ではないか、他の興味深いコンパイラ出力に似ています。

標準が何かを強調している場合、それは何らかの正当な理由と論理的根拠によるものでなければなりません。

では、問題は、なぜ代表者向けのCIL標準が、封印されたものと仮想的なものを同時に強調するのかということです。

キャッチはここにありますか?:

ボディはVESによって自動的に作成されるため、ボディを持たないものとします。

これらのメソッドの呼び出し時にVES/CLRで生成された本体を実行できるように、仮想としてマークされていますか?

4

4 に答える 4

5

タイプ定義を確認するために使用した逆アセンブラにつまずきました。これは、ILをC#などの認識可能な言語に変換し直す必要があります。これは一般的に完全に忠実に行うことはできません。ILのルールはC#言語のルールと同じではありません。これはデリゲートだけで発生するのではなく、C#コードで仮想として宣言していなくても、インターフェイスの実装メソッドも仮想です。

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

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

于 2012-07-10T14:01:33.397 に答える
4

私の質問で述べたように、この封印された仮想異常は実際にはCIL標準によって義務付けられています。InvokeCIL標準がデリゲートメソッドに具体的に言及している理由は不明でBeginInvokeあり、継承されたクラスEndInvokeをシールすることを義務付けると同時に仮想である必要があります。Delegate

callvirtまた、SSCLIコードを調べた後、JITコンパイラの内部最適化により、封印されたクラスの仮想メソッドでの呼び出しが、追加のnullチェックを使用して通常の呼び出しに自動的に変換されることを学びました。callvirtこれは、ILで仮想としてマークされているにもかかわらず、Invoke(または他の2つ)メソッドが命令によって呼び出されたときに、デリゲートがパフォーマンスに影響を与えないことを意味します。

デリゲートのinvokeが呼び出されると、CLRは、ILコードをコンパイルして「通常の」メソッドで実行する本体を生成するのではなく、このメソッド用に高度に最適化された本体を自動的に出力します。virtualこれは、ILでマークされることとは何の関係もありません。

また、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つのデリゲートメソッドを呼び出すと、実際には他のメソッド(つまり、「通常の」仮想メソッドのような実行時ポリモーフィズム)が呼び出されることを強調するか、将来に対応するためのものです。デリゲートに関連する架空の拡張。

于 2012-07-12T11:40:01.227 に答える
3

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

于 2012-07-10T12:36:21.253 に答える
1

代表者に固有のものではないようです。私はこの例を試しました:

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

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

そしてILDasmでは、Test()の実装のためにこれを取得します:

.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キーワードではない可能性があります。

于 2012-07-10T12:44:08.613 に答える