13

私たちのコードベースでいくつかのコードを見ていましたが、これがどのように/なぜ機能しているのか理解できません(無限再帰によるスタックオーバーフローを引き起こしていません)。以下に同等のコードをいくつか貼り付けました。 クラス P1 で定義され、クラス P2 でオーバーライドされた仮想メソッド Foo(B) があります。P2 は、プライベートな非仮想メソッド Foo(A) も定義します。B は A から派生します。P2::Foo(B) は最後に呼び出しがあります: Foo(b)。これがスタック オーバーフローに終わることを期待しています。ただし、出力は次のとおりです。 P2::Foo 仮想 P2::Foo プライベート 非仮想

この場合、オーバーライドされたメソッドでの Foo への 2 番目の呼び出しは、非仮想メソッド Foo を取得しているようです。P1 (コードのアンコメント) で同様の操作を行うと、再帰によって Foo を無限に呼び出すことになります。

質問: (最後に!) 1. 元の仮想メソッドとオーバーライドされたメソッドで動作が異なるのはなぜですか? 1 つは自分自身を呼び出し、もう 1 つは別のメソッドを呼び出すのはなぜですか? 2.どこかに指定された優先順位はありますか? private 修飾子を public に変更すると、どちらの場合も、非仮想メソッドを呼び出すことになることに注意してください (P2 をこのようにインスタンス化しても、 P2 p2 = new P2( の代わりに P1 p2 = new P2(); )。 );) 仮想メソッド定義内にある場合を除いて、非仮想バージョンが優先されるようです。これは本当ですか?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
public class P1
{
    static void Main(string[] args)
    {
        B b = new B();
        P2 p2 = new P2();
        p2.Foo(b);
        // Uncomment code to cause infinite recursion
        //P1 p1 = new P1();
        //p1.Foo(b);
    }

    private void Foo(A a)
    {
        Console.WriteLine("P1::Foo Private Non-Virtual");
    }

    public virtual void Foo(B b)
    {
        Console.WriteLine("Inside P1::Foo");
        // Uncomment code to cause infinite recursion
        // Foo(b);
    }
}

public class P2 : P1
{
    private void Foo(A a)
    {
        Console.WriteLine("P2::Foo Private Non-Virtual");
    }

    public override void Foo(B b)
    {
        Console.WriteLine("P2::Foo Virtual");
        Foo(b);
    }
}

public class A
{
    public int a = 10;
}

public class B : A
{
    public int b = 20;
}

}

4

2 に答える 2

11

これは、オーバーロード解決では、派生型で定義されたオーバーロードを選択できない場合にのみ、継承されたメンバーが参照されるためです。仕様(バージョン4)から:

たとえば、メソッド呼び出しの候補のセットには、override(§7.4)とマークされたメソッドは含まれず、派生クラスのメソッドが適用可能な場合(§7.6.5.1)、基本クラスのメソッドは候補になりません。

あなたの質問に具体的に取り組むために:

元の仮想メソッドとオーバーライドされたメソッドで動作が異なるのはなぜですか?

オーバーライドされたメソッドは派生クラスで定義され、適用可能なオーバーロードがそのクラスに存在するため、仮想メソッドは考慮されません。オーバーライドは考慮されないため、オーバーライドメソッドは考慮されません。

一方が自分自身を呼び出し、もう一方が別のメソッドを呼び出すのはなぜですか?

派生クラスの動作は上で説明されています。基本クラスでは、過負荷解決の最適な候補は仮想メソッド自体です。これは、より具体的であるためです(BはAから派生します)。

どこかに優先順位が指定されていますか?

はい、C#言語仕様(Visual Studio 2012バージョンの仕様のMSDNページへのリンク)。

どちらの場合も、プライベート修飾子をpublicに変更すると、非仮想メソッドが呼び出されることに注意してください(P2をこのようにインスタンス化した場合でも、P2の代わりにP1 p2 = new P2();、P2 p2 = new P2(); );)

この場合、アクセシビリティは重要な問題ではありません。求めている過負荷の解決は、仮想メソッドのオーバーライドでの呼び出しサイトに関係するため、変数のタイプp2も関係ありません。仮想ディスパッチは、変数の静的タイプに関係なく、P2inの呼び出しがオーバーライドを呼び出すことを保証します。のMain()呼び出しサイトでは、受信者は暗黙的に、静的タイプは常にです。P2override void Foo(B b)thisP2

仮想メソッド定義内にある場合を除いて、非仮想バージョンが優先されるようです。これは本当ですか?

完全ではありません。上で説明したように、優先されるのは非仮想メソッドではなく、レシーバーのタイプ(つまり、メソッドが呼び出されているオブジェクト参照の静的タイプ)で定義されたメソッドです。

于 2012-09-11T17:36:31.737 に答える
8

これはよく誤解される C# の機能です。派生クラスのメソッドが適用可能な場合、基本クラスのメソッドは候補になりません。

于 2012-09-11T17:45:25.103 に答える