7

ビジター パターンとジェネリック メソッドを試してみたところ、C#.NET にある種の不一致が見つかりました。AFAIK C# コンパイラは、ジェネリック メソッドよりも明示的なオーバーロードを優先するため、次のコードは次のようになります。

public abstract class A
{
    public abstract void Accept(Visitor v);
}

public class B : A
{
    public override void Accept(Visitor v)
    { v.Visit(this); }
}

public class C : A
{
    public override void Accept(Visitor v)
    { v.Visit(this); }
}

public class D : A
{
    public override void Accept(Visitor v)
    { v.Visit(this); }
}

public class Visitor
{
    public void Visit(B b)
    { Console.WriteLine("visiting B"); }

    public void Visit(C c)
    { Console.WriteLine("visiting C"); }

    public void Visit<T>(T t)
    { Console.WriteLine("visiting generic type: " + typeof(T).Name); }
}

class Program
{

    static void Main()
    {
        A b = new B();
        A c = new C();
        A d = new D();

        Visitor v = new Visitor();

        b.Accept(v);
        c.Accept(v);
        d.Accept(v);
    }
}

生成される出力は (予想どおり) です。

visiting B
visiting C
visiting generic type: D

ただし、この Visitor パターンの実装では、Visitor クラスを交換できません。抽象クラス VisitorBase を導入し、呼び出しをオーバーロードに転送すると、smth が生成されます。私には予想外……。

public abstract class A
{
    public abstract void Accept(VisitorBase v);
}

public class B : A
{
    public override void Accept(VisitorBase v)
    { v.Visit(this); }
}

public class C : A
{
    public override void Accept(VisitorBase v)
    { v.Visit(this); }
}

public class D : A
{
    public override void Accept(VisitorBase v)
    { v.Visit(this); }
}

public abstract class VisitorBase
{
    public abstract void Visit<T>(T t);
}

public class Visitor : VisitorBase
{
    protected void VisitImpl(B b)
    { Console.WriteLine("visiting B"); }

    protected void VisitImpl(C c)
    { Console.WriteLine("visiting C"); }

    protected void VisitImpl<T>(T t)
    { Console.WriteLine("visiting generic type: " + typeof(T).Name); }

    public override void Visit<T>(T t)
    {
        VisitImpl(t); //forward the call to VisitorImpl<T> or its overloads
    }
}

class Program
{

    static void Main()
    {
        A b = new B();
        A c = new C();
        A d = new D();

        VisitorBase v = new Visitor();

        b.Accept(v);
        c.Accept(v);
        d.Accept(v);
    }
}

出力は次のようになります。

visiting generic type: B
visiting generic type: C
visiting generic type: D

ジェネリック メソッドはジェネリック メソッドのみを優先しますか? 明示的なオーバーロードが呼び出されないのはなぜですか?

4

4 に答える 4

6

オーバーロードは静的に行われるため、を呼び出すときVisitImpl(t)、コンパイラーは、この呼び出しが表す単一の最良のオーバーロードされたメソッドを選択する必要があります(存在する場合)。typeパラメータTは何でもかまいません。互換性のある唯一のメソッドは汎用メソッドであり、したがって、の呼び出しからのすべての呼び出しVisit<T>(T t)ですVisitImpl<T>(T t)

編集

あなたはC++のバックグラウンドから来ているように見えるので、おそらくC ++テンプレートはC#ジェネリックとは非常に異なることに注意する価値があります。特に、C#に特化するようなものはありません。そのため、表示される動作が予期しないものになる可能性があります。C#コンパイラは、ジェネリックメソッドが呼び出される可能性のあるタイプごとに異なるコードを出力しませんVisit(1)(つまり、C#コンパイラは、 andを呼び出すときに同じジェネリックメソッドを呼び出し、タイプとVisit("hello")でメソッドの特殊化を生成しません)。実行時に、CLRは型固有のメソッドを作成しますが、これはコンパイル後に発生し、過負荷の解決に影響を与えることはできません。intstring

編集-さらに詳細

非ジェネリックメソッドが静的に適用可能であることがわかっている場合、 C#はジェネリックメソッドよりも非ジェネリックメソッドを優先します。

C#コンパイラは、任意の呼び出しサイトで呼び出す単一のメソッドを選択します。オーバーロードを完全に忘れて、メソッドにそれぞれ異なる名前を付けます。問題の呼び出しサイトで呼び出すことができる名前が変更されたメソッドはどれですか?一般的なものだけ。したがって、3つの名前が衝突して過負荷の解決が開始された場合でも、そのサイトで適用できる唯一の過負荷であり、選択された方法です。

于 2010-01-29T17:56:43.937 に答える
1

私が理解しているように、私は非常に間違っている可能性がありますが、コンパイル時にジェネリック関数の訪問は実際には元の型のボックス化解除のようなものを実行します。コンパイル時に型が実行される必要があることは論理的にわかりますが、C# コンパイラは型を保持している間は Visit 関数を介して VisitImpl 関数に到達できないため、元の b.visit(v) はコンパイル時にボックス化されていないと見なされます。 . これにより、Visit メソッドが呼び出されたときに一致するすべての型のジェネリックを経由する必要があります。

編集:私は自分のがらくたを読んだだけなので、私が何を意味するのかを明確にするために:

コンパイラは、b.Visit のリンクを汎用呼び出しとして保持します。それは適合し、ジェネリックとラベル付けされています。コンパイラは、Visit->VisitImpl の個別のリンクを必要に応じて型付きおよび/またはジェネリック メソッドとして保持します。コンパイラは、b.Visit (ジェネリックとして) -> VisitImpl からのリンクを型どおりに保持できません。b.Visit() -> VisitImpl からのパスはジェネリックを経由する必要があるため、ジェネリック型として保持されるため、ジェネリック VisitImpl が優先されます。

于 2010-01-29T18:06:04.680 に答える
1

オーバーロードとオーバーライドを混同しているようです。

オーバーロードとは、パラメーターの型が異なる同じ名前の複数のメソッドを提供する場合です。

クラスフー
   | |
   +- void Qux(A arg)
   +- void Qux(B arg)
   +- void Qux(C arg)

オーバーライドとは、同じ (仮想) メソッドの複数の実装を提供する場合です。

class Foo class Bar : Foo class Baz : Foo
   | | | | | |
   +- 仮想 void Quux() +- オーバーライド void Quux() +- オーバーライド void Quux()

C# は単一のディスパッチを実行します。

  • 呼び出されたメソッドのオーバーロードは、コンパイル時に決定されます。

  • オーバーライドされたメソッドの実装は、実行時に決定されます。

ビジター パターンは、Visit メソッドの適切な実装にメソッド呼び出しをディスパッチすることにより、後者を利用します。複数のディスパッチがある言語では、実行時に適切なオーバーロードが選択されるため、ビジター パターンは必要ありません。

于 2010-01-29T18:13:15.053 に答える
0

ジェネリックスはコンパイラー機能であるため、コンパイル時に利用可能な情報のみを使用して、呼び出すメソッドを決定します。実行時に、変数の実際のタイプを判別する必要があります。コンパイラは、変数bがタイプA、cがタイプA、dがタイプAであることのみを認識します。Aを使用するメソッドがないため、一般的なオーバーロードである最適なオーバーロードを選択します。

于 2010-01-29T17:55:31.900 に答える