C# 中更简洁的 Visitor 实现

Less verbose Visitor implementation in C#

假设您有一个抽象 BaseClass 和一些派生的 classes,并且您需要访问带有 VisitorList<BaseClass>。代码将是:

class Visitor
{
    public void Visit(Derived1 visitable) { /*do something*/ }
    public void Visit(Derived2 visitable) { /*do something*/ }
    public void Visit(Base visitable) { thrown new InvalidOperationException(); }
    //other derivatives
}

class Base
{
    public virtual void Accept(Visitor visitor) { visitor.Visit(this); }
}

class Derived1 : Base
{
    //override is mandatory to have Visit(Derived1 visitable) called
    public override void Accept(Visitor visitor) { visitor.Visit(this); }
}

class Derived2 : Base
{
    //override is mandatory to have Visit(Derived2 visitable) called
    public override void Accept(Visitor visitor) { visitor.Visit(this); }
}

然后你就可以在如下方法中使用所有这些东西:

var baseClassList = new List<BaseClass>();
//fill baseClassList somehow

var visitor = new Visitor();

foreach(var visitable in baseClassList)
    visitable.Accept(visitor);

我认为这对于在不同派生的 classes 上简单分派一些操作来说有点过分了。

此外,链从调用 Accept 而不是 Visit 开始是违反直觉的。

另外,如果你添加一个新的 Derived3 class,你总是必须做两件事:Visitor 中的新重载和 [=15= 的覆盖] 方法中的新派生 class。通常你会忘记第二点,得到一个运行时错误,以及其他烦人的事情。

我正在寻找 C# 中 Visitor 模式的更简单、更简洁、更安全的实现。可能吗?

给定 2 个假设:

  1. 您可以使用 C# 4.0 或更高版本
  2. BaseClass 是抽象的,BaseClass 没有 "default" 行为(即 Visitor 在每个派生上都有不同的特定行为)

我找到了解决方案:

class DynamicVisitor
{
    public void Visit(BaseClass b)
    {
        Visit((dynamic)b);
    }

    public void Visit(Derived1 b) { /*do something*/ }

    public void Visit(Derived2 b) { /*do something*/ }
}

你这样使用它:

var baseClassList = new List<BaseClass>();
//fill baseClassList somehow

var visitor = new Visitor();

foreach(var visitable in baseClassList)
    visitor.Visit(visitable);

这样,您不需要任何 Accept 方法,也不需要它覆盖所访问的 类。 您只需要在 Visitor 上编写重载。 重载 Visit(BaseClass b) 确保任何传递的参数至少派生自 BaseClass,但 dynamic 转换使 Visitor 在运行时选择特定的重载。 显然,如果一个实例恰好是 BaseClass 类型,那么您将无限递归调用 Visit(Base b),那是因为我认为 BaseClass 是抽象的:这样您就无法拥有一个 Visit(Base b) 的实例那个类型。


编辑:

我用这个大概的负载做了一些性能测试:

  1. A BaseClass 和 17 DerivedClasses 实现了 IVisitable 接口;
  2. 包含 BaseClass 数百万个实例的列表可供访问;
  3. a ClassicVisitorDynamicVisitor 都实现了 IVisitor 接口;
  4. 所有Accept方法只调用Visit方法,所有Visit方法都是空的,以最大化测量的时间差异。
  5. 在调试和发布模式下使用 Stopwatch 测量的时间。

结果:DynamicVisitor 慢了 10 倍以上(~5/600 毫秒对~50 毫秒)。

因此,此解决方案适用于您需要对访问者进行少量调用的情况。