8

C#4.0の仕様は次のとおりです。

仮想メソッドが呼び出されると、その呼び出しが行われるインスタンスのランタイムタイプによって、呼び出す実際のメソッド実装が決まります。非仮想メソッドの呼び出しでは、インスタンスのコンパイル時のタイプが決定要因になります。

最初は、これは初期化と関係があると思いました。たとえば、2つの初期化が与えられます。

BaseClass bcDerived = new Derived();vsBaseClass bcBase = new BaseClass();

ヘルパークラスのオーバーロード:

public virtual void Method(Derived d)
{
     Console.WriteLine("Result = derived called");
}

public virtual void Method(BaseClass d)
{
     Console.WriteLine("Result = base called");
}

Methodこの場合、呼び出しはvirtualキーワードの影響を受けません。をマークしたかどうかに関係なくvirtual、最小の派生オーバーロードが呼び出されます。Derivedクラスでのみoverride、メソッド呼び出しが変更されます。

では、「ランタイムタイプ」と「コンパイル時タイプ」はどういう意味ですか?それらはメソッド呼び出しにどのように影響しますか?

4

3 に答える 3

6

これは、仮想メソッドと非仮想メソッド、および呼び出しがどのように発生するかという問題です。引用している仕様の部分は、変数のメソッド呼び出しを扱います-呼び出しbcDerived.SomeMethod()ではなく、呼び出しfoo.SomeMethod(bcDerived)ます。

引用している仕様は、非仮想メソッドがある場合を示しています。

public class A
{
    public void Foo() { Console.WriteLine("A.Foo"); }
    public virtual void Bar() { Console.WriteLine("A.Bar"); }
}
public class B : A
{
    public new void Foo() { Console.WriteLine("B.Foo"); }
    public override void Bar() { Console.WriteLine("B.Bar"); }
}

次に、呼び出されるメソッドは、コンパイル時にコンパイラによって決定されます。これにより、次のようになります。

A someInst = new B();
someInst.Foo();

これは非仮想メソッドであるため、Aのどのサブクラスが参照されているかに関係なくA.Foo()、これにより、が呼び出されます。someInst

ただし、仮想メソッドがある場合、callvirt命令はコンパイラーによって指定され、実行時に決定が移動します。この意味は:

 someInst.Bar();

B.Bar()ではなく、を呼び出しますA.Bar()

あなたの場合、(仕様が参照しているという意味で)仮想メソッドを呼び出しているのではなく、標準のメソッド解決を行っています。C#仕様の7.5.3は、過負荷の解決を詳細に扱っています。あなたの場合、引数リスト(bcDerived)はコンパイラーによって検査され、型として定義されているように見えBaseClassます。これに対する「最適な一致」はpublic virtual void Method(BaseClass d)、パラメータリストが引数リストと直接一致するため、コンパイル時に使用されます。

メソッドのオーバーロード解決は、仕様を見ると、仮想メソッド呼び出しを直接有効にするのではなく、型間の暗黙的な変換のみを調べます。

于 2013-01-21T16:43:20.427 に答える
1

この場合、引数のコンパイル時タイプは、呼び出すオーバーロードを決定するために常に使用されます。仮想ディスパッチは、メソッドが呼び出されているオブジェクトの実行時タイプによって異なります。

コンパイル時の型はコンパイラによって決定されたオブジェクトの型であり、実行時型はコードが実行されているときの実際の型です。あなたの例を使用するには:

BaseClass bcDerived = new Derived()

コンパイル時の型はですBaseClassが、実行時の型はですDerived

影響を理解するには、クラスを少し拡張する必要があります。

class BaseClass 
{ 
  public virtual void SomeMethod() 
  {
    Console.WriteLine("In base class");
  }
}

class Derived : BaseClass
{ 
  public override void SomeMethod() 
  {
    Console.WriteLine("In derived class");
  }
}

これで、呼び出しは、実装が呼び出されるか、実装が呼び出されるかbcDerived.SomeMethod()に関するランタイムタイプによって異なります。bcDerivedBaseClassDerived

Eric Lippertは、.Netでの仮想ディスパッチに関する非常に優れた3部構成のシリーズを作成しました(そのうちの1部はここにあります)。主題をより完全に理解するためにそれらを読むことを強くお勧めします。

于 2013-01-21T16:41:07.800 に答える
1
Using these two classes as examples:

public class Parent
{
    public void NonVirtual()
    {
        Console.WriteLine("Nonvirtual - Parent");
    }
    public virtual void Virtual()
    {
        Console.WriteLine("Virtual - Parent");
    }
}

public class Child : Parent
{
    public override void Virtual()
    {
        Console.WriteLine("Virtual - Child");
    }

    public void NonVirtual()
    {
        Console.WriteLine("Nonvirtual - Child");
    }
}

仮想と非仮想の違いは、このコードで最も明確にわかります。

Parent childAsParent = new Child();
childAsParent.Virtual();
childAsParent.NonVirtual();

これは印刷します:

仮想-子
非仮想-親

仮想メソッドの場合、実行時に、の型がchildAsParent子であることがわかり、子の定義を実行しVirtualます。非仮想メソッドの場合、変数のコンパイル時の型がであるParentことがわかり、実際のインスタンスがであるという事実を無視しChild、親の実装を使用します。

virtualパラメータのタイプに基づいて、メソッドのどのオーバーロードが使用されるかを決定するために使用されることはありません。呼び出すメソッドのオーバーロードを決定することは、実行時にではなく、常にコンパイル時に行われます(使用していない場合)。したがって、この例では、変数のコンパイル時のタイプに基づいて、dynamic常にのオーバーロードが選択されます。Method

于 2013-01-21T16:45:49.627 に答える