Why do I need to Cast Visitor Object to Dynamic for Double Dispatch?

China☆狼群 提交于 2020-05-14 08:49:10

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!