23

次のコードを考えてみましょう (少し長いですが、従うことができれば幸いです)。

class A
{
}

class B : A
{
}

class C
{
    public virtual void Foo(B b)
    {
        Console.WriteLine("base.Foo(B)");
    }
}

class D: C
{
    public override void Foo(B b)
    {
        Console.WriteLine("Foo(B)");
    }

    public void Foo(A a)
    {
        Console.WriteLine("Foo(A)");
    }
}

class Program
{
    public static void Main()
    {
        B b = new B();
        D d = new D ();
        d.Foo(b);
    }
}

このプログラムの出力が "Foo(B)" だと思うなら、あなたは私と同じ船に乗っているでしょう: 完全に間違っています! 実際には「Foo(A)」を出力します

クラスから仮想メソッドを削除すると、C期待どおりに機能します。「Foo(B)」が出力されます。

コンパイラは、より派生したクラスであるA場合にバージョンを選択するのはなぜですか?B

4

5 に答える 5

15

答えは、C# 仕様のセクション 7.3およびセクション 7.5.5.1にあります。

呼び出すメソッドを選択するための手順を詳しく説明しました。

  • N=Foo最初に、T ( ) で宣言されたN ( ) という名前のすべてのアクセス可能なメンバーのセットと、T ( T=class D) の基本型class Cが構築されます。オーバーライド修飾子を含む宣言はセットから除外されます( D.Foo(B) は exclude です) 。

    S = { C.Foo(B) ; D.Foo(A) }
    
  • メソッド呼び出しの候補メソッドのセットが構築されます。前のメンバー検索で見つかった M に関連付けられたメソッドのセットから始めて、セットは引数リスト AL ( ) に関して適用可能なメソッドに絞り込まれますAL=B。セット削減は、セット内の各メソッド TN に次の規則を適用することで構成されます。T ( T=class D) は、メソッド N ( N=Foo) が宣言されている型です。

    • N が AL に関して適用できない場合 (セクション 7.4.2.1 )、N はセットから削除されます。

      • C.Foo(B)ALに関して適用可能
      • D.Foo(A)ALに関して適用可能

        S = { C.Foo(B) ; D.Foo(A) }
        
    • N が AL に関して適用可能である場合 (セクション 7.4.2.1)、T の基本型で宣言されたすべてのメソッドが set から削除されます。C.Foo(B)セットから削除されます

          S = { D.Foo(A) }
      

最終的に勝者はD.Foo(A)です。


抽象メソッドが C から削除された場合

抽象メソッドが C から削除された場合、初期セットはS = { D.Foo(B) ; D.Foo(A) }であり、オーバーロード解決規則を使用して、そのセット内で最適な関数メンバーを選択する必要があります。

この場合、勝者はD.Foo(B)です。

于 2010-09-09T07:11:02.953 に答える
10

B がより派生したクラスであるのに、コンパイラが A を取るバージョンを選択するのはなぜですか?

他の人が指摘したように、言語仕様がそうするように言っているので、コンパイラはそうします。

これは満足のいく答えではないかもしれません。自然なフォローアップは、「そのように言語を指定するという決定の根底にある設計原則は何ですか?」となるでしょう。

これは、StackOverflow と私のメールボックスの両方でよく寄せられる質問です。簡単な答えは、「この設計は、Brittle Base Class ファミリーのバグを軽減する」です。

この機能の説明と、このように設計されている理由については、この件に関する私の記事を参照してください。

http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

さまざまな言語が脆弱な基本クラスの問題にどのように対処するかに関するその他の記事については、この件に関する私の記事のアーカイブを参照してください。

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

先週の同じ質問に対する私の回答は、これと非常によく似ています。

基本クラスで宣言された署名が無視されるのはなぜですか?

さらに、関連する、または重複する質問が 3 つあります。

C#のオーバーロード解決?

メソッドは解像度をオーバーロードし、Jon Skeet の頭の体操

なぜこれが機能するのですか?メソッドのオーバーロード + メソッドのオーバーライド + ポリモーフィズム

于 2010-09-09T14:57:05.553 に答える
2

非仮想メソッドの場合、メソッドが呼び出される変数のコンパイル時の型が使用されるためだと思います。

非仮想の Foo メソッドがあるため、そのメソッドが呼び出されます。

このリンクには非常に良い説明がありますhttp://msdn.microsoft.com/en-us/library/aa645767%28VS.71%29.aspx

于 2010-09-09T07:02:47.420 に答える
2

したがって、仕様に従って動作する方法は次のとおりです(コンパイル時、およびドキュメントを正しくナビゲートした場合):

Dコンパイラは、メソッド名と引数リストに基づいて、型とその基本型から一致するメソッドのリストを識別します。Fooこれは、 からの暗黙的な変換がある型のパラメーターを 1 つ取る、という名前のメソッドBが有効な候補であることを意味します。これにより、次のリストが生成されます。

C.Foo(B) (public virtual)
D.Foo(B) (public override)
D.Foo(A) (public)

このリストから、オーバーライド修飾子を含む宣言は除外されます。つまり、リストには次のメソッドが含まれるようになりました。

C.Foo(B) (public virtual)
D.Foo(A) (public)

この時点で、一致する候補のリストがあり、コンパイラは何を呼び出すかを決定します。ドキュメント7.5.5.1 Method invocationsには、次のテキストがあります。

N が A に関して適用可能である場合 (セクション 7.4.2.1 )、T の基本型で宣言されたすべてのメソッドがセットから削除されます。

これは基本的にD、 で宣言された適用可能なメソッドがある場合、基本クラスのメソッドがリストから削除されることを意味します。この時点で、勝者は次のとおりです。

D.Foo(A) (public)
于 2010-09-09T07:36:24.767 に答える
0

別のクラスを実装するときは、メソッドの確実な実装を取得するためにツリーをはるかに見ていくと思います。呼び出されるメソッドがないため、基本クラスを使用しています。

public void Foo(A a){
    Console.WriteLine("Foo(A)" + a.GetType().Name);
    Console.WriteLine("Foo(A)" +a.GetType().BaseType );
}

それは私が.Netのプロではないことを推測しています

于 2010-09-09T07:11:48.290 に答える