2

「The C# Programming Language」という本で、Eric Lippert は次のように述べています。

ここで微妙な点は、オーバーライドされた仮想メソッドは、それをオーバーライドするクラスのメソッドではなく、それを導入したクラスのメソッドと見なされることです。

この声明の意味は何ですか?派生クラスを扱っていない限り、オーバーライドされたメソッドは決して呼び出されないため、オーバーライドされた仮想メソッドがそれを導入したクラスのメソッドであると見なされる場合 (またはそれ以外の場合) が問題になるのはなぜですか?

4

3 に答える 3

3

あるタイプの参照が別のタイプのオブジェクトを指している場合に重要です。

例:

public class BaseClass {
  public virtual int SomeVirtualMethod() { return 1; }
}

public class DerivedClass : BaseClass {
  public override int SomeVirtualMethod() { return 2; }
}

BaseClass ref = new DerivedClass();
int test = ref.SomeVirtualMethod(); // will be 2

仮想メソッドは基本クラスのメンバーであるため、基本クラスの型の参照を使用してオーバーライド メソッドを呼び出すことができます。そうでない場合は、オーバーライド メソッドを呼び出すために派生型の参照が必要になります。

メソッドをオーバーライドするのではなくシャドウする場合、シャドウするメソッドは派生クラスのメンバーです。参照のタイプに応じて、元のメソッドまたはシャドウ メソッドを呼び出します。

public class BaseClass {
  public int SomeMethod() { return 1; }
}

public class DerivedClass : BaseClass {
  public new int SomeMethod() { return 2; }
}

BaseClass ref = new DerivedClass();
int test = ref.SomeMethod(); // will be 1

DerivedClass ref2 = ref;
int test2 = ref2.SomeMethod(); // will be 2
于 2012-08-26T15:38:47.133 に答える
1

これが本からの完全な引用です:

ここでの微妙な点は、オーバーライドされた仮想メソッドは、それを導入したクラスのメソッドであり、それをオーバーライドするクラスのメソッドではないと見なされることです。過負荷解決ルールは、基本タイプのメンバーよりも派生タイプのメンバーを優先する場合があります。メソッドをオーバーライドしても、そのメソッドがこの階層内のどこにあるかは「移動」しません。

このセクションの冒頭で、C#はバージョン管理を念頭に置いて設計されていることに気づきました。これは、「脆弱な基本クラスのシンドローム」がバージョン管理の問題を引き起こすのを防ぐのに役立つ機能の1つです。

完全な引用は、Eric Lippertが、仮想メソッドがどのように機能するかだけでなく、メソッドのオーバーロードについて具体的に話していることを明確にしています。

例として、次のプログラムについて考えてみます。

class Base
{
    public virtual void M2(int i) { }
}

class Derived : Base
{
    public void M1(int i) { Console.WriteLine("Derived.M1(int)"); }
    public void M1(float f) { Console.WriteLine("Derived.M1(float)"); }

    public override void M2(int i) { Console.WriteLine("Derived.M2(int)"); }
    public void M2(float f) { Console.WriteLine("Derived.M2(float)"); }

    public static void Main()
    {
        Derived d = new Derived();
        d.M1(1);
        d.M2(1);
    }
}

多くの開発者は、出力が

Derived.M1(int)
Derived.M2(float)

より良い一致であるにもかかわらず、なぜd.M2(1)呼び出すのでしょうか?Derived.M2(float)Derived.M2(int)

コンパイラがinが何M1d.M1(1)参照しているかを判断するとき、コンパイラはとが両方M1(int)M1(float)導入されていることを確認Derivedするため、両方のオーバーロードが適切な候補になります。コンパイラは、整数引数に最適なものとして選択M1(int)します。M1(float)1

コンパイラーがinが何M2d.M2(1)参照しているかを判別しているとき、コンパイラーは、inM2(float)が導入されDerived、適用可能な候補であることを確認します。過負荷解決ルールによると、「派生クラスのメソッドが適用可能な場合、基本クラスのメソッドは候補になりません」。該当するため、このルールは候補になるM2(float)ことを防ぎます。整数引数によく一致し、でオーバーライドされたとしてもM2(int)、それは.のメソッドであると見なされます。M2(int)DerivedBase

于 2012-08-26T16:58:54.483 に答える
0

オーバーライドされた仮想メソッドが、それをオーバーライドするクラスではなく、それを導入するクラスに属していることを理解すると、クラス メンバーがバインドされる方法を理解しやすくなります。オブジェクトを使用する場合を除きdynamic、C# のすべてのバインドはコンパイル時に解決されます。aBaseClassが仮想メソッドを宣言しfoo、 をDerivedClass:BaseClassオーバーライドする場合、型の変数fooを呼び出そうとするコードは常に仮想メソッド "slot" にバインドされ、それが実際のメソッドを指します。fooBaseClassBaseClass.fooDerivedClass.foo

C# では、C++ とは異なり、ジェネリック型のメンバーは具体的なジェネリック型ではなく、ジェネリックの制約に従ってバインドされるため、この理解はジェネリックを扱う場合に特に重要です。たとえば、 methodSubDerivedClass:DerivedClassを作成したと、 new virtualmethodfoo()を定義したがあったとしDoFoo<T>(T param) where T:BaseClass {param.foo();}ます。メソッドが として呼び出された場合でも、呼び出しparam.foo()は にバインドされます。を呼び出す前にパラメーターが にキャストされた場合、呼び出しは にバインドされますが、コンパイラーは、それがより具体的なものになる時期を判断できず、 に存在しないものにはバインドできません。BaseClass.fooDoFoo<SubDrivedClass>(subDerivedInstance)SubDerivedClassfooSubDrivedClass.foo()DoFoo<T>TBaseClassBaseClass

ちなみに、クラスが基本クラスのメンバーを同時にオーバーライドし、新しいメンバーを作成できると便利な場合があります。たとえば、ReadableFoo読み取り専用の抽象プロパティを持つ抽象基本クラスがある場合、クラスMutableFooがそのプロパティのオーバーライドを提供し、同じ名前で読み書き可能なプロパティを定義できると便利です。残念ながら、.net ではそれが許可されていません。このような制限がある場合、別の名前のメソッドReadableFooを呼び出して値を取得する、具体的な非仮想の読み取り専用プロパティを提供するのが最善の方法かもしれません。protected abstractそうすれば、派生クラスは読み取り専用プロパティを読み取り/書き込みプロパティ (読み取り用に同じ仮想メソッドを呼び出すか、書き込み用に新しい (おそらく仮想) メソッドを呼び出す) でシャドウすることができます。

(以下は未確認)

class BaseClass
{
    public virtual void foo() {Console.WriteLine("BaseClass.Foo");
}
class DerivedClass:BaseClass
{
    public override void foo() {Console.WriteLine("Derived.Foo");
}
class SubDerivedClass:DerivedClass
{
    public new virtual void foo() {Console.WriteLine("SubDerived.Foo");
}
class MegaDerivedClass:SubDerivedClass
{
    public override void foo() {Console.WriteLine("MegaDerived.Foo");
}
void DoFoo1<T>(T param) where T:BaseClass 
    {
        param.foo();
}
void DoFoo1<T>(T param) where T:SubDerivedClass 
    {
        param.foo();
}
void test(void)
{
    var megaDerivedInstance = new MegaDerivedClass();
    DoFoo1<MegaDerivedClass>(megaDerivedInstance);
    DoFoo2<MegaDerivedClass>(megaDerivedInstance);
}

SubDerivedClass には、 と の 2 つの仮想foo()メソッドがBaseClass.foo()ありSubDerivedClass.foo()ます。AMegaDerivedClassには同じ 2 つのメソッドがあります。SubDerivedClass()オーバーライドしようとする派生クラスfooはオーバーライドされSubDerivedClass.foo()、影響を与えないことに注意してくださいBaseClass.foo()。上記の宣言では、 の派生物はSubDerivedClassオーバーライドできませんBaseClass.FooSubDerivedClassまた、インスタンスまたはそのサブクラスをキャストするDerivedClassか、仮想メソッドを呼び出し用BaseClassに公開することにも注意してください。BaseClass.foo

ちなみに、 のメソッド宣言が でSubDerivedClassあった場合friend new virtual void foo() {Console.WriteLine("SubDerived.Foo");、同じアセンブリ内の他のクラスがオーバーライドすることはできませんBaseClass.foo()(オーバーライドしようとすると がオーバーライドさfoo()れるためSubDerivedClass.foo()) 。SubDerivedClassSubDerivedClass.foo()BaseClass.foo()

于 2012-08-26T15:54:10.583 に答える