4

これはなんて素晴らしいサイトなのか、私はここに潜んでいて、何年もの間他の人の質問を読んでいましたが、今は自分のものを持っています。

私の同僚は、以下のようなクラスを作成しました。それを見るとすぐに機能しないことがわかりましたが、なぜ機能しないのか説明はありません。

彼がそれをaとして宣言するときに期待したのControlItem<Button>は、ベースを使用してDraw()を呼び出すときにDraw(Button)メソッドが呼び出されるということです。代わりに、常に例外をスローすることになります。

これは共分散の問題ですか?

public abstract class ControlItem
{
    public ControlItem()
    {
    }

    abstract public void Draw();
}

public class ControlItem<T> : ControlItem where T : Control, new()
{
    public T MyControl { get; set; }

    private ControlItem()
    {       }

    public ControlItem(T control)
        : base()
    {
        MyControl = control;
    }

    public override void Draw()
    {
        Draw(this.MyControl);
    }

    public void Draw(Control cntrl)
    {
        throw new NotImplementedException();
    }

    public void Draw(Button button)
    {
        //Do some work
    }
}
4

3 に答える 3

4

これは共分散の問題ですか?

いいえ、これは静的ディスパッチと動的ディスパッチの問題です。静的ディスパッチとは、オーバーロードされたメソッド呼び出しが、渡された変数の型に基づいてコンパイル時に適切な型にバインドされることを意味します。

class Base { }
class Derived : Base { }

class Foo
{
    void Test()
    {
        Base a = new Base();
        Overload(a);    // prints "base"

        Derived b = new Derived();
        Overload(b);    // prints "derived"

        // dispatched based on c's declared type!
        Base c = new Derived();
        Overload(c);    // prints "base"
    }

    void Overload(Base obj)    { Console.WriteLine("base"); }
    void Overload(Derived obj) { Console.WriteLine("derived"); }
}

動的ディスパッチとは、変数に格納されているオブジェクトの実際の型に基づいて、関数が実行時にバインドされることを意味します。

class Base
{
    public virtual void Override() { Console.WriteLine("base"); }
}

class Derived : Base
{
    public override void Override() { Console.WriteLine("derived"); }
}

class Foo
{
    void Test()
    {
        Base a = new Base();
        a.Override();   // prints "base"

        Derived b = new Derived();
        b.Override();    // prints "derived"

        // dynamically dispatched based type of object stored in c!
        Base c = new Derived();
        c.Override();    // prints "derived"
    }

    void Overload(Base obj) { Console.WriteLine("base"); }
    void Overload(Derived obj) { Console.WriteLine("derived"); }
}

最後の印刷は、2 つの違いを示しています。C# は、ほとんどのクラスベースの OOP 言語と同様に、this暗黙的なパラメーターの動的ディスパッチ (「単一ディスパッチ」と呼ばれます) のみをサポートします。つまり、オーバーライドされたメソッドは動的にディスパッチされますが、オーバーロードされたメソッドはそうではありません。

単一のディスパッチ言語で複数のディスパッチを偽造する典型的な解決策は、ビジター パターンを使用することです。

于 2009-04-08T15:52:02.007 に答える
2

これは、コンパイラがその型がコントロールであることを確実に認識しているだけであるため、常に Control 引数を使用してメソッドにバインドするためです。別の方法で処理する必要がある場合は、 Draw() メソッドに明示的なチェックを追加する必要があります。

public override void Draw() {
   Button btn = MyControl as Button;
   if (btn != null) {
      Draw(btn);
   } else {
      Draw(this.MyControl);
   }
}

これはあまり「一般的」ではないことに注意してください...しかし、特殊なケースではうまくいくかもしれません。

于 2009-04-08T15:40:27.370 に答える
2

munificent の回答に基づいて作成するには: C++ テンプレートとは異なり、C# ジェネリックはコンパイル時にインスタンス化されません。ジェネリック型に対して C# コンパイラによって生成されるコードは、コードで使用する特殊化に完全に依存しません。コンパイラは、制約を満たす型パラメーターの置換に対して機能する 1 つのコードを吐き出します。完全に指定されたジェネリック型のインスタンスがインスタンス化される実行時まで、JIT コンパイラは型パラメーターに固有のコードを作成しません。

生成されたコードは、制約の基準を満たすすべてのものに対して機能するため、C# コンパイラは、制約から推測できる限り、メンバーを(not )MyControl型の変数として扱います。コンパイラはクラスの汎用コードを発行する必要があるため、知っていることに基づいて呼び出すメソッドを選択する必要があります。 ControlTMyControlButtonDraw(Control)

于 2009-04-08T16:25:13.987 に答える