问题
I'd like to know why I need to cast both my visitable object and my visitor to dynamics in order for double dispatch to work without repeated overloads of the Accept method. If I cast either to dynamic but not the other, the generic base class's visit method is called. But if I cast both, then sub-classes' visit methods are called.
I have implemented the standard visitor pattern with generics. I have a visitor interface as follows:
public interface ITreeVisitorVoid<TPayload>
{
void Visit(ITreeBase<TPayload> tree);
}
I have a base implementation of the visitor interface:
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);
}
}
}
And I have a base Tree class which has an accept method:
public virtual void Accept(ITreeVisitorVoid<TPayload> visitor)
{
(visitor as dynamic).Visit(this as dynamic);
}
If I cast just one or none of the objects to a dynamic, then the base Visit method is always called. Only when I cast both will the overloading happen dynamically at runtime. In other words this won't work:
(visitor as dynamic).Visit(this);
Neither will this:
visitor.Visit(this as dynamic);
Based on some reading I did I can see that overload resolution happens at compile time for non-dynamic types.
Overload resolution of virtual methods
Double dispatch in C#?
Now, I understand why, based on that reading, the object this
must be cast to a dynamic- otherwise the overload will resolve at compile time and we're doomed. However, I'm a little confused as to why the cast on the visitor calling object itself is necessary. I thought polymorphism would give me that for free. Of course, the sub-classes don't truly override the Visit method, so I'm guessing something like this is happening:
At compile time, the tree is a dynamic, but the visitor is typed as the base class
ITreeVisitorVoid<TPayload>
.This base class only has a Visit method defined over the base tree type
ITreeBase<TPayload>
At runtime, then, the base visitor class's visit method is called. But since it is marked virtual, some "overrides" table is checked to see if there's an override. Which there isn't.
So since an override isn't found, the base method is used
Meanwhile, any over-loaded Visit method in a subclass is overlooked
On the other hand, when the visitor itself is cast to a dynamic, it forces the program to look at all visit methods defined on the
runtime
type. Which will then pick up any overloads that exist.
So My Question Can Be Equivalently Stated as: Is the above explanation correct? And if not, what is going on?
An example of such an overload in my codebase, buried a couple of levels down the in the hierarchy, is the following:
/// <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++;
}
}
来源:https://stackoverflow.com/questions/58309692/why-do-i-need-to-cast-visitor-object-to-dynamic-for-double-dispatch