Почему мне нужно преобразовать объект посетителя в динамический для двойной отправки? - PullRequest
0 голосов
/ 09 октября 2019

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

Я реализовал стандартный шаблон посетителей с обобщениями. У меня есть интерфейс посетителя следующим образом:

public interface ITreeVisitorVoid<TPayload>
{
    void Visit(ITreeBase<TPayload> tree);
}

У меня есть базовая реализация интерфейса посетителя:

public abstract class TreeVisitorVoidBase<TPayload> : BaseClass, ITreeVisitorVoid<TPayload>
{
    protected virtual void DefaultOperation(ITreeBase<TPayload> tree) { }

    public virtual void Visit(ITreeBase<TPayload> tree)
    {
        DoOpAndVisitAllChildren(tree, DefaultOperation);
    }

    protected void DoOpAndVisitAllChildren(ITreeBase<TPayload> tree, Action<ITreeBase<TPayload>> operation)
    {
        operation(tree);
        IEnumerable<ITreeBase<TPayload>> children = tree.Children;
        foreach (ITreeBase<TPayload> child in children)
        {
            child.Accept(this);
        }
    }
}

И у меня есть базовый класс Tree, у которого есть метод accept:

    public virtual void Accept(ITreeVisitorVoid<TPayload> visitor)
    {
        (visitor as dynamic).Visit(this as dynamic);
    }

Если я приведу только один или ни один из объектов к динамическому объекту, то всегда вызывается базовый метод Visit. Только когда я разыграю оба, перегрузка произойдет динамически во время выполнения. Другими словами, это не сработает:

        (visitor as dynamic).Visit(this);

Так же не сработает:

        visitor.Visit(this as dynamic);

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

Разрешение перегрузки виртуальных методов

Двойная диспетчеризация в C #?

Теперь я понимаю, почему, основываясь на этом чтенииобъект this должен быть приведен к динамическому типу, иначе перегрузка разрешится во время компиляции, и мы обречены. Однако меня немного смущает вопрос о том, почему необходимо использовать приведение к посетителю, вызывающему сам объект. Я думал, что полиморфизм даст мне это бесплатно. Конечно, подклассы на самом деле не переопределяют метод Visit, поэтому я предполагаю, что происходит нечто подобное:

  • Во время компиляциидерево является динамическим, но посетитель вводится как базовый класс ITreeVisitorVoid<TPayload>.

  • Этот базовый класс имеет только метод Visit, определенный для базового типа дерева ITreeBase<TPayload>

  • Затем во время выполнения вызывается метод посещения базового класса посетителей. Но поскольку он помечен как виртуальный, проверяется некоторая таблица «переопределений», чтобы увидеть, есть ли переопределение. Чего нет.

  • Так как переопределение не найдено, используется базовый метод

  • Между тем, любое пере- загружено Метод посещения в подклассе упускается из виду

  • С другой стороны, когда сам посетитель приводится к динамике, он заставляет программу смотреть все методы посещения, определенные для типа runtime. Который затем обнаружит любые перегрузки, которые существуют.

Так что мой вопрос может быть эквивалентно сформулирован так: Правильно ли приведенное выше объяснение? А если нет, что происходит?

Пример такой перегрузки в моей кодовой базе, скрытой на несколько уровней ниже в иерархии, следующий:

/// <summary>
/// Count only the leaf nodes of the tree in question, not the intermediate nodes
/// </summary>
class LeaftCounterVisitorImpl<TPayload> : NodeCounterVisitorBase<TPayload>, INodeCounterVisitor<TPayload>
{
    public virtual void Visit(ILeafBase<TPayload> leaf)
    {
        if (leaf == null)
        {
            Log.ErrorFormat("Skipping over null leaf during leaf counting operation");
        }
        Count++;
    }
}
...