我想知道为什么我需要将我的可访问对象和我的访问者都转换为动态对象,以使双重调度能够正常工作,而不会重复接受Accept方法。如果我将其中一个转换为动态而不转换为动态,则将调用通用基类的visit方法。但是,如果我同时转换了两者,则将调用子类的访问方法。
我已经使用泛型实现了标准的访客模式。我有一个访问者界面,如下所示:
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类:
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);
基于一些阅读,我确实看到非动态类型的重载解析发生在编译时。
Overload resolution of virtual methods
现在,我理解了为什么基于该读取结果,必须将对象this
强制转换为动态对象,否则重载将在编译时解决,我们就注定了。但是,对于为什么需要对访问者调用对象本身进行强制转换,我有些困惑。我以为多态会免费提供给我。当然,子类并没有真正地 override Visit方法,所以我猜正在发生这样的事情:
在编译时,树是动态的,但是访问者被键入为基类ITreeVisitorVoid<TPayload>
。
此基类仅在基树类型ITreeBase<TPayload>
然后,在运行时,将调用基本访问者类的visit方法。但是,由于它被标记为虚拟,因此将检查一些“替代”表以查看是否存在替代。没有。
因此,由于找不到覆盖,因此使用了基本方法
与此同时,子类中任何超载的 访问方法都会被忽略
另一方面,当访问者本身被转换为动态对象时,它将强制程序查看在runtime
类型上定义的 all 访问方法。然后,它将拾取存在的所有重载。
所以我的问题可以等效地表达为:以上解释是 正确?如果没有,那是怎么回事?
在我的代码库中这样的重载示例如下:
/// <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++;
}
}