2

非仮想メソッドは静的にバインドされていることを理解しています。つまり、私が理解している限りでは、どのメソッドがどのオブジェクトで呼び出されるかについて、コンパイル時にそれ自体が認識されていることを意味します。この決定は、オブジェクトの静的タイプに基づいて行われます。私を混乱させているのは、( classではなく)インターフェイスと静的バインディングです。

このコードを考えてみましょう。

public interface IA
{
    void f();
}
public class A : IA
{
    public void f() {  Console.WriteLine("A.f()"); }
}
public class B : A 
{
    public new void f() {  Console.WriteLine("B.f()"); }
}

B b = new B();
b.f();  //calls B.f()     //Line 1

IA ia = b as IA;
ia.f(); //calls A.f()     //Line 2

デモコード: http://ideone.com/JOVmi

を理解していLine 1ます。コンパイラは、 の静的型が であることを知っているため、b.f()が呼び出されることを知ることができます。B.f()bB

しかし、コンパイラはコンパイル時にどのようia.f()に を呼び出すかをどのように決定するのA.f()でしょうか? オブジェクトの静的タイプは何iaですか? そうじゃないIA?しかし、それはインターフェースであり、の定義はありませんf()。では、なぜそれが機能するのですか?

ケースをより不可解にするために、次のstatic方法を考えてみましょう。

static void g(IA ia)
{
   ia.f(); //What will it call? There can be too many classes implementing IA!
}

コメントにあるように、インターフェイスを実装するクラスが多すぎる可能性があります。その場合、どのメソッドを呼び出すかをIAコンパイルで静的に決定するにはどうすればよいでしょうか? ia.f()つまり、次のように定義されたクラスがあるとします。

public class C : A, IA 
{
    public new void f() { Console.WriteLine("C.f()"); }
}

ご覧のとおりC、 は とは異なり、からの派生に加えてB実装しています。つまり、ここでは動作が異なります。IAA

g(new B()); //inside g(): ia.f() calls A.f() as before!
g(new C()); //inside g(): ia.f() doesn't calls A.f(), rather it calls C.f()

デモコード : http://ideone.com/awCor

これらすべてのバリエーション、特にインターフェイスと静的バインディングがどのように連携するかを理解するにはどうすればよいでしょうか?

さらにいくつか ( ideone ):

C c = new C();
c.f(); //calls C.f()

IA ia = c as IA;
ia.f(); //calls C.f()

A a = c as A;
a.f(); //doesn't call C.f() - instead calls A.f()

IA iaa = a as IA;
iaa.f(); //calls C.f() - not A.f()

これらすべてと、C# コンパイラによって静的バインディングがどのように行われるかを理解するのを手伝ってください。

4

2 に答える 2

6

しかし、コンパイラはコンパイル時にどのようia.f()に を呼び出すかをどのように決定するのA.f()でしょうか?

そうではありません。に含まれるオブジェクト インスタンスでia.f()が呼び出されることを認識しています。この呼び出しオペコードを発行し、呼び出しが実行されたときにランタイムにそれを認識させます。IA.f()ia

サンプル コードの下半分に対して出力される IL は次のとおりです。

    .locals init (
            class B   V_0,
            class IA  V_1)
    IL_0000:  newobj instance void class B::'.ctor'()
    IL_0005:  stloc.0
    IL_0006:  ldloc.0
    IL_0007:  callvirt instance void class B::f()
    IL_000c:  ldloc.0
    IL_000d:  stloc.1
    IL_000e:  ldloc.1
    IL_000f:  callvirt instance void class IA::f()
    IL_0014:  ret

callvirt両方の場合に使用されることに注意してください。これが使用されるのは、ターゲット メソッドが非仮想であることをランタイムが独自に判断できるためです。(さらに、引数に対してcallvirt暗黙の null チェックを実行しますが、実行しません。)thiscall

この IL ダンプは、他のすべての質問に答える必要があります。つまり、コンパイラは最終的なメソッド呼び出しを解決しようとさえしません。それはランタイムの仕事です。

于 2011-08-27T18:24:30.413 に答える
1

静的バインディングとは、あなたが思っている以外のことを意味します。「アーリーバインディング」とも呼ばれ、レイトバインディングの反対であり、C#バージョン4ではdynamicキーワードを使用し、すべてのバージョンでリフレクションを使用できます。遅延バインディングの主な特徴は、適切な引数が渡されたことを確認することは言うまでもなく、呼び出されたメソッドが存在することさえ確認できないことです。何かが失敗した場合、実行時例外が発生します。また、ランタイムがメソッドを検索し、引数を検証し、呼び出しスタックフレームを構築するために追加の作業を行う必要があるため、速度も遅くなります。

インターフェイスまたは仮想メソッドを使用する場合、これは問題ではありません。コンパイラはすべてを事前に検証できます。結果のコードは非常に効率的です。これにより、インターフェイスと仮想メソッドを実装するために必要な間接メソッド呼び出し(別名「動的ディスパッチ」)が発生しますが、非仮想インスタンスメソッドのC#でも使用されます。元C#チームメンバーからのこのブログ投稿に記載されています。この機能を実現するCLR配管は、「メソッドテーブル」と呼ばれます。C ++のvテーブルとほぼ同じですが、メソッドテーブルには、非仮想メソッドを含むすべてのメソッドのエントリが含まれています。インターフェイス参照は、このテーブルへの単なるポインタです。

于 2011-08-27T20:03:28.447 に答える