Я хотел бы знать, почему мне нужно привести и мой посещаемый объект, и моего посетителя в динамику, чтобы двойная диспетчеризация работала без повторных перегрузок метода 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++;
}
}