2009-04-15 10 views
9

C#のバーチャルとオーバーライドメカニズムが内部的にどのように機能するかについての話題は、プログラマーの間で議論されてきましたが、30時間後には、 )以下を参照:C#バーチャルとオーバーライドの内部動作

簡単なコードの使用:

public class BaseClass 
{ 
    public virtual SayNo() { return "NO!!!"; } 
} 

public class SecondClass: BaseClass 
{ 
    public override SayNo() { return "No."; } 
} 

public class ThirdClass: SecondClass 
{ 
    public override SayNo() { return "No..."; } 
} 

class Program 
{ 
    static void Main() 
    { 
    ThirdClass thirdclass = new ThirdClass(); 
    string a = thirdclass.SayNo(); // this would return "No..." 

    // Question: 
    // Is there a way, not using the "new" keyword and/or the "hide" 
    // mechansim (i.e. not modifying the 3 classes above), can we somehow return 
    // a string from the SecondClass or even the BaseClass only using the 
    // variable "third"? 

    // I know the lines below won't get me to "NO!!!" 
    BaseClass bc = (BaseClass)thirdclass; 
    string b = bc.SayNo(); // this gives me "No..." but how to I get to "NO!!!"? 
    } 
} 

を私は単純に(せずに、ほとんどの派生インスタンスを使用して、基本クラスまたは中間派生クラスのメソッドを取得することはできませんだと思います3つのクラスのメソッドシグネチャを変更する)。しかし、私は私の理解を確認し、確信したいと思います...

ありがとう。

+0

つもりでした(SecondClassを言うのは)? –

+0

いいえ。追加または変更するクラスはもうありません。 – henry000

答えて

1

オーバーライドの基本メソッドにアクセスすることはできません。どのようにオブジェクトをキャストしても、インスタンス内の最後のオーバーライドは常に使用されます。

+0

これは完全に真実ではありません。 Jaredsの答えのように、いつでも "base"を使用して、基底クラス(この場合はSecondClass)でオーバーライドされたメソッドを呼び出すことができます。 – Pete

+1

あなたがオブジェクトの外にいて、質問者が質問したことは、あなたができないのです。もちろん、callvirtの代わりにILコールを使用して回避することもできますが、C#は静的メソッドを除いて呼び出すことはないという点で特別です。 – grover

7

サンプルを変更せずにリフレクションを変更することなく、方法はありません。仮想システムの目的は、CLRが何をしていても、派生したものを呼び出すことを強制することです。

これを回避するには、いくつかの方法があります。

オプション1:ここでの主な問題は、あなたが仮想を呼び出したいということです:あなたはこれがSecondClass.SayNo

オプション2の呼び出しを強制するThirdClass

public void SayNoBase() { 
    base.SayNo(); 
} 

に次のメソッドを追加することができます方法は仮想ではない。 C#は、基本修飾子を介してこれを行う方法の1つだけを提供します。これにより、非仮想的な方法で独自のクラス内のメソッドを呼び出すことが不可能になります。これを第2の方法とプロキシに分解して修正することができます。

public overrides void SayNo() { 
    SayNoHelper(); 
} 

public void SayNoHelper() { 
    Console.WriteLine("No"); 
} 
+0

さらに、 パブリッククラスThirdClassを持っていた場合:BaseClass { base.SayNo(); } これはNOを返します。 – Pete

2

確かに...

BaseClass bc = new BaseClass(); 
    string b = bc.SayNo(); 

が実行される実装は、基礎となるオブジェクトの実際の型ではなく、それは変数の型に基づいていることを意味し「仮想」ぬいぐるみ...実際のオブジェクトがThirdClassの場合、それはあなたがどのようにキャストしても、あなたが得る実装です。上で説明した動作をしたい場合は、メソッドを仮想にしないでください。

"何がポイントですか?"それは '多形性'のためです。コレクションやメソッドのパラメータをいくつかの基本型として宣言し、派生型を混在させて渡すことができます。ただし、コード内では各オブジェクトがそれぞれの仮想メソッド呼び出しのために実行される実際の実装は、各オブジェクトのACTUAL tyoeのクラス定義で定義されたその実装になります。

0

反射を使用してフィールドを引き出します。

あなたがtypeof演算(BaseClassの)からの反射を利用してMETHODINFOをやってのける場合でも、あなたはまだあなたのオーバーライドされたメソッドを実行することになります

14

C#が、これを行うことはできませんが、それcallの代わりを使用してILに実際に可能ですcallvirt。したがって、と組み合わせてReflection.Emitを使用することで、C#の制限を回避することができます。

これはどのように動作するかを示す非常に簡単な例です。あなたが本当にこれを使うつもりなら、すてきな関数の中に入れて、それを異なるデリゲート型で動作させるように努めてください。

delegate string SayNoDelegate(BaseClass instance); 

static void Main() { 
    BaseClass target = new SecondClass(); 

    var method_args = new Type[] { typeof(BaseClass) }; 
    var pull = new DynamicMethod("pull", typeof(string), method_args); 
    var method = typeof(BaseClass).GetMethod("SayNo", new Type[] {}); 
    var ilgen = pull.GetILGenerator(); 
    ilgen.Emit(OpCodes.Ldarg_0); 
    ilgen.EmitCall(OpCodes.Call, method, null); 
    ilgen.Emit(OpCodes.Ret); 

    var call = (SayNoDelegate)pull.CreateDelegate(typeof(SayNoDelegate)); 
    Console.WriteLine("callvirt, in C#: {0}", target.SayNo()); 
    Console.WriteLine("call, in IL: {0}", call(target)); 
} 

プリント:

callvirt, in C#: No. 
call, in IL: NO!!! 
+1

私はCLRの最初の数ページだけをC#で読んだことがありますが、このような回答をすれば、休みを取って終了することができます! – overslacked

+0

@overslacked、私も。私は本を​​完成させるのに十分な時間を欲しがっています:CLR via C#。 – Attilah

2

baseを使用してC#では唯一の即時ベースで動作します。ベースベースのメンバーにアクセスすることはできません。

ILで実行できることについて、他の人が私にパンチを打つように見えます。

しかし、私はコードgenをやったやり方にはいくつかの利点があると思うので、とにかく投稿します。

私が違ったのは、C#コンパイラを使ってオーバーロード解決と汎用引数の置換を行うための式ツリーを使用することです。

そのようなものは複雑であり、あなたがそれを手助けすることができればあなた自身を複製する必要はありません。 あなたのケースでは、コードは次のように動作します:

var del = 
    CreateNonVirtualCall<Program, BaseClass, Action<ThirdClass>> 
    (
     x=>x.SayNo() 
    ); 

あなたは一度だけ、それをコンパイルする必要がなるようにあなたはおそらく、読み取り専用の静的フィールドにデリゲートを保存するとよいでしょう。

あなたは3つのジェネリック引数を指定する必要があります。

  1. 所有者のタイプを - これは、あなたが「CreateNonVirtualCall」を使用していなかった場合のコードを呼び出していたクラスです。

  2. ベースクラス - これは、あなたが

  3. デリゲート型から非仮想呼び出しを作りたいクラスです。これは、 "this"引数に追加のパラメータを指定して呼び出されるメソッドのシグニチャを表す必要があります。これを排除することは可能ですが、code genメソッドでもっと多くの作業が必要になります。

このメソッドは、単一の引数、呼び出しを表すラムダを取ります。それはコールでなければならず、コールでなければなりません。あなたがコードgenを拡張したいなら、より複雑なものをサポートすることができます。

簡潔にするため、ラムダ本体はラムダパラメータにしかアクセスできないように制限されており、ラムダ本体をラムダパラメータに直接渡すことができます。すべての式タイプをサポートするようにメソッド本体のコードgenを拡張すると、この制限を取り除くことができます。しかし、それはいくつかの仕事を取るだろう。戻ってきたデリゲートで何でもできますので、制限はそれほど大きなものではありません。

このコードは完全ではありません。それはより多くの検証を使用することができ、式ツリーの制限のために "ref"または "out"パラメータでは機能しません。

voidメソッド、値を返すメソッド、および汎用メソッドを使用してサンプルケースでテストしていました。私は、しかし、あなたは動作しないいくつかのエッジケースを見つけることができると確信しています。いずれの場合においても

が、ここではILゲンコードです:あなたはBasClassを継承するためにあなたのクラスのいずれかのために

public static TDelegate CreateNonVirtCall<TOwner, TBase, TDelegate>(Expression<TDelegate> call) where TDelegate : class 
{ 
    if (! typeof(Delegate).IsAssignableFrom(typeof(TDelegate))) 
    { 
     throw new InvalidOperationException("TDelegate must be a delegate type."); 
    } 

    var body = call.Body as MethodCallExpression; 

    if (body.NodeType != ExpressionType.Call || body == null) 
    { 
     throw new ArgumentException("Expected a call expression", "call"); 
    } 

    foreach (var arg in body.Arguments) 
    { 
     if (arg.NodeType != ExpressionType.Parameter) 
     { 
      //to support non lambda parameter arguments, you need to add support for compiling all expression types. 
      throw new ArgumentException("Expected a constant or parameter argument", "call"); 
     } 
    } 

    if (body.Object != null && body.Object.NodeType != ExpressionType.Parameter) 
    { 
     //to support a non constant base, you have to implement support for compiling all expression types. 
     throw new ArgumentException("Expected a constant base expression", "call"); 
    } 

    var paramMap = new Dictionary<string, int>(); 
    int index = 0; 

    foreach (var item in call.Parameters) 
    { 
     paramMap.Add(item.Name, index++); 
    } 

    Type[] parameterTypes; 


    parameterTypes = call.Parameters.Select(p => p.Type).ToArray(); 

    var m = 
     new DynamicMethod 
     (
      "$something_unique", 
      body.Type, 
      parameterTypes, 
      typeof(TOwner) 
     ); 

    var builder = m.GetILGenerator(); 
    var callTarget = body.Method; 

    if (body.Object != null) 
    { 
     var paramIndex = paramMap[((ParameterExpression)body.Object).Name]; 
     builder.Emit(OpCodes.Ldarg, paramIndex); 
    } 

    foreach (var item in body.Arguments) 
    { 
     var param = (ParameterExpression)item; 

     builder.Emit(OpCodes.Ldarg, paramMap[param.Name]); 
    } 

    builder.EmitCall(OpCodes.Call, FindBaseMethod(typeof(TBase), callTarget), null); 

    if (body.Type != typeof(void)) 
    { 
     builder.Emit(OpCodes.Ret); 
    } 

    var obj = (object) m.CreateDelegate(typeof (TDelegate)); 
    return obj as TDelegate; 
} 
+1

クールな作品。間違いなくそれを掲示する価値がある! –

+0

ありがとうございます。 (最小!コメント長) –

関連する問題