перегрузка общего метода c#, не согласовывающаяся с абстрактным Шаблоном "посетитель"

при экспериментировании с Шаблоном "посетитель" и общим методом я нашел своего рода несоответствие в 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

Однако эта реализация Шаблона "посетитель" не позволяет обмениваться классом Посетителя. Представление абстрактного класса VisitorBase и перевод вызова к перегрузкам производят что-то неожиданное для меня....

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

Общие методы только предпочитают общие методы? Почему никакие явные перегрузки не называют?

Большое спасибо,
Ovanes

6
задан ovanes 29 January 2010 в 17:54
поделиться

4 ответа

Как я понимаю, и я мог бы быть очень неправильным, во время компиляции мочеиспускательный визит на самом деле выполняет своего рода распаковка исходного типа. Хотя мы можем логически видеть, что типы должны проходить через время компиляции, компилятор C # не может провести его через функцию посещения к функции Visitimpl, удерживая типы, поэтому оригинал B.Visit (V) считается Unboxed при компиляции Отказ Учитывая это, он должен быть направлен через универсальный для всех типов, которые соответствуют, когда требуется метод посещения.

Отредактируйте: чтобы уточнить, что я имею в виду, потому что я просто читал свое собственное дерьмо:

Компилятор содержит ссылку для B.Visit в виде общего вызова. Он подходит и помечен универсальным. Компилятор удерживает отдельные ссылки для посещения навестиние, как это напечатано, так и / или общие методы. Компилятор не может удерживать ссылку от B.Visit (AS Generic) -> Visitimpl, как напечатано. Поскольку путь от B.Visit () -> Visitimpl должен пройти через универсальный, он удерживает его как универсальный тип, и поэтому общий посетителей является предпочтительным.

1
ответ дан 17 December 2019 в 00:09
поделиться

Generics - это функция компилятора, поэтому только информация о компиляции используется для определения того, какой метод следует вызывать. То, что вы делаете, потребуется во время выполнения, чтобы определить, какой фактический тип переменной является. Компилятор знает только, что переменная B имеет тип A, C типа A, а D имеет тип A. Он выбирает лучшую перегрузку, которая является универсальным, так как нет метода, который требуется A.

0
ответ дан 17 December 2019 в 00:09
поделиться

Перегрузка выполняется статически, поэтому при вызове Visitimpl (T) компилятор должен выбрать один лучший перегруженный метод, который представляет этот вызов (если есть один). Поскольку тип типа T может быть что-нибудь, единственный метод, который совместим, является универсальным методом, и, следовательно, все вызовы из посетителей (T t) вызов Visitimpl (T t) .

Редактировать

Похоже, вы можете прийти с фона C ++, поэтому, возможно, стоит отметить, что шаблоны C ++ сильно отличаются от генеризма C #; В частности, нет такой вещи, как специализация в C #, что может быть, почему поведение, которое вы видите, является неожиданным. Компилятор C # не излучает другой код для различных типов, при которых можно назвать универсальный метод (то есть C # Compiler вызывает тот же универсальный метод при вызове посещения (1) . ] и визит («Hello») , он не генерирует специализации метода на типов int и string ). Во время выполнения CLR создает определенные методы типа, но это происходит после компиляции и не может повлиять на разрешение перегрузки.

Отредактируйте - еще более разработка

C # предпочитает нерегистые методы для универсальных методов , когда нетребющий метод статистически известен .

Компилятор C # выберет один метод для вызова на любом заданном вызове. Забудьте о перегрузке полностью, и дайте своим методам каждое другое имя; Какие из этих переименованных методов могут быть вызваны на вызове рассматриваемого сайта? Только общий. Следовательно, даже когда три имени сталкиваются и разрешение перегрузки, то есть единственная перегрузка, которая применима на этом сайте и выбран метод.

5
ответ дан 17 December 2019 в 00:09
поделиться

Кажется, вы запутаете перегрузку и переопределение.

Перегрузка , когда вы предоставляете несколько методов с с именем , которые отличаются в типов параметра:

class Foo
   |
   +- void Qux(A arg)
   +- void Qux(B arg)
   +- void Qux(C arg)

, переопределение , когда вы предоставляете Несколько реализаций метода : :

class Foo                  class Bar : Foo             class Baz : Foo
   |                          |                           |
   +- virtual void Quux()     +- override void Quux()     +- override void Quux()

C # Выполняет Одиночную рассылку :

  • Перегрузка призывающего метода определяется при составлении компиляции.

  • Реализация переопределенного метода определяется во время выполнения.

Узор посетителя использует последнее, отправляя вызов метода к правильной реализации метода посещения. На языках с несколькими рассылками шаблон посетителя не нужен, потому что правильная перегрузка выбирается во время выполнения.

1
ответ дан 17 December 2019 в 00:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: