8

(呼び出しクラスまたは呼び出しメソッドの)ジェネリック型引数where T : BaseがT == Derivedの新しいメソッドで制約されている場合、Derivedは呼び出されず、代わりにBaseのメソッドが呼び出されます。

タイプTは、実行前に認識されている必要があるのに、メソッド呼び出しで無視されるのはなぜですか?

更新:しかし、制約がwhere T : IBaseBaseクラスのメソッドのようなインターフェイスを使用している場合は呼び出されます(インターフェイスのメソッドではなく、これも不可能です)。
つまり、システムは実際にタイプをはるかに検出し、タイプの制約を超えることができるということです。では、クラス型制約の場合、なぜ型制約を超えないのでしょうか。
これは、インターフェイスを実装するBaseクラスのメソッドに、メソッドの暗黙的なオーバーライドキーワードがあることを意味しますか?

テストコード:

public interface IBase
{
    void Method();
}

public class Base : IBase 
{
    public void Method()
    {

    }
}

public class Derived : Base
{
    public int i = 0;

    public new void Method()
    {
        i++;
    }
}

public class Generic<T>
    where T : Base
{
    public void CallMethod(T obj)
    {
        obj.Method();  //calls Base.Method()
    }

    public void CallMethod2<T2>(T2 obj)
        where T2 : T
    {
        obj.Method();  //calls Base.Method()
    }
}

public class GenericWithInterfaceConstraint<T>
    where T : IBase
{
    public void CallMethod(T obj)
    {
        obj.Method();  //calls Base.Method()
    }

    public void CallMethod2<T2>(T2 obj)
        where T2 : T
    {
        obj.Method();  //calls Base.Method()
    }
}

public class NonGeneric
{
    public void CallMethod(Derived obj)
    {
        obj.Method();  //calls Derived.Method()
    }

    public void CallMethod2<T>(T obj)
        where T : Base
    {
        obj.Method();  //calls Base.Method()
    }

    public void CallMethod3<T>(T obj)
        where T : IBase
    {
        obj.Method();  //calls Base.Method()
    }
}

public class NewMethod
{
    unsafe static void Main(string[] args)
    {
        Generic<Derived> genericObj = new Generic<Derived>();
        GenericWithInterfaceConstraint<Derived> genericObj2 = new GenericWithInterfaceConstraint<Derived>();
        NonGeneric nonGenericObj = new NonGeneric();
        Derived obj = new Derived();

        genericObj.CallMethod(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        genericObj.CallMethod2(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        genericObj2.CallMethod(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        genericObj2.CallMethod2(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        nonGenericObj.CallMethod(obj);  //calls Derived.Method()
        Console.WriteLine(obj.i);

        nonGenericObj.CallMethod2(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        nonGenericObj.CallMethod3(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        obj.Method();  //calls Derived.Method()
        Console.WriteLine(obj.i);
    }
}

出力:

0
0
0
0
1
1
1
2
4

4 に答える 4

7

オブジェクトを使用する場合を除いてdynamic、C#は、ジェネリックスを使用している場合でも、コンパイル時に常にメソッドをバインドします。仮想メソッド呼び出しは、実装メソッドではなく仮想メソッドスロットにバインドされるため、派生クラスオブジェクトで実行されると、派生クラスの実装に転送されます。スロットが指すメソッドは実行時に決定されますが、スロットへのバインドはコンパイル時に行われます。newではなく派生クラスメソッドが宣言さoverrideれている場合、派生クラスを使用してバインドされたコードは派生クラスメソッドを使用しますが、基本クラスを使用してバインドされたコードは基本クラスメソッドを使用します。

これが当てはまる理由を理解するために、そうでない場合を想像してみてください。Baseクラスがメソッドint Foo()を宣言し、クラスがをDerived:Base宣言した場合はどうなりますかnew string Foo()。制約のあるジェネリッククラスが型のオブジェクトでT:Baseメソッドを呼び出そうとした場合、そのメソッドの戻り型はどうあるべきですか?FooT

于 2012-05-24T16:04:13.733 に答える
6

これはT、のセマンティクスを持つように制約されているためですBase。実行時に型バインディングで何が起こっているのかを正確に伝えることはできませんが、これは私の知識に基づく推測です。

メソッドを適切にオーバーライドしていませんが、代わりに「new」を介して非表示にしています。基本クラスへの参照を使用する場合は、非表示をバイパスします。これは隠れが落ちるところです。

他のメンバーを非表示にするメンバーは、非表示になっているタイプへの参照を使用している場合にのみ適用されます。基本クラスへの参照を使用して、非表示のメンバーをいつでもバイパスできます。

var derived = new Derived();
var baseRef = (Base)derived;
baseRef.Method(); // calls Base.Method instead of Derived.Method.

メソッドを適切にオーバーライドしてこのコードを機能させるには、メソッドをvirtual基本クラスのようにマークoverrideし、派生クラスのようにマークします。

class Base
{
    public virtual void Method() {}
}

class Derived : Base
{
    public override void Method() {}
}

これを証明し、一般的な制約をに変更するwhere T : Derivedと、「新しい」メンバーにヒットするはずです。

于 2012-05-24T15:57:41.073 に答える
0

これは、演算子newの性質によるものです。オーバーライドとは異なり、基本メソッドと同じ名前の関数を作成します。この関数は、基本メソッドをマスクしますが、オーバーライドしません。

そのため、適切なキャストがないと、参照がBaseタイプの場合、元のメソッドが呼び出されます。

于 2012-05-24T15:58:15.787 に答える
0

キーワードは、メソッドをオーバーロードするのではなく、new単に非表示にします。非ジェネリックが期待どおりに機能しているように見える理由は、メソッドシグネチャがの代わりにをCallMethod期待しているためです。 DerivedBase

ここでは、ジェネリックは実際には犯人ではありません。メソッドシグネチャをに変更するCallMethod(Base obj)と、ジェネリック実装と同じ「予期しない」動作が表示され、次の出力が得られます。

0
0
0
0
0
0
0
1

Base.Method仮想化し、次のようにオーバーライドする場合Derived.Method

public class Base 
{
    public virtual void Method()
    {

    }
}

public class Derived : Base
{
    public int i = 0;

    public override void Method()
    {
        i++;
    }
}

次の出力が得られます。

1
2
3
4
5
6
7
8

編集:質問の更新された出力に一致するように更新されました。

于 2012-05-24T16:09:15.463 に答える