简体   繁体   English

在C#中不太复杂的Visitor实现

[英]Less verbose Visitor implementation in C#

Suppose you have an abstract BaseClass and some derived classes and that you need to visit a List<BaseClass> with a Visitor . 假设您有一个抽象BaseClass和一些派生类,并且需要使用Visitor访问一个List<BaseClass> The code would be: 该代码将是:

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); }
}

Then you can use all this stuff in a method like: 然后,您可以通过以下方法使用所有这些东西:

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

var visitor = new Visitor();

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

I think this is a bit too much for a simple dispatching of some operations on different derived classes. 我认为对于在不同的派生类上简单地分派某些操作来说,这有点太多了。

Also, it is counter-intuitive that the chain starts from calling Accept and not Visit . 同样,从链开始调用Accept而不是Visit开始是违反直觉的。

Also, if you add a new Derived3 class, you always have to do 2 things: a new overload in the Visitor AND the override of the Accept method in the new derived class. 另外,如果添加新的Derived3类,则始终必须做两件事: Visitor的新重载和新派生类中的Accept方法的覆盖。 Often you forget the second point, getting a runtime error, and other annoying things. 通常,您会忘记第二点,即出现运行时错误和其他令人讨厌的事情。

I'm looking for some simpler and less verbose and more secure implementation of a Visitor pattern in C#. 我正在寻找一些更简单,更简洁,更安全的C# Visitor模式实现。 Is it possible? 可能吗?

Given 2 hypotheses: 给定2个假设:

  1. You can use C# 4.0 or more recent 您可以使用C#4.0或更高版本
  2. BaseClass is abstract and there isn't a "default" behavior for BaseClass (ie, the Visitor has a different specific behavior on EVERY derived) BaseClass是抽象的,没有一个“默认”的行为BaseClass (即Visitor中有各个衍生不同的特定行为)

I found a solution: 我找到了解决方案:

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

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

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

And you use it this way: 您可以通过以下方式使用它:

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

var visitor = new Visitor();

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

This way, you don't need any Accept method, nor its overrides on the visited classes. 这样,您不需要任何Accept方法,也不需要在访问的类上重写它。 You only need to write the overloads on the Visitor . 您只需要在Visitor上写入重载。 The overload Visit(BaseClass b) ensures that any passed parameter derives at least from the BaseClass , but the dynamic cast makes the Visitor to choose the specific overload at runtime. 重载Visit(BaseClass b)确保任何传递的参数至少从BaseClass派生,但是dynamic强制转换使Visitor在运行时选择特定的重载。 Obviously, if an instance is exactly of type BaseClass , you have an infinite recursive call of Visit(Base b) , and that's because I supposed that BaseClass is abstract: this way you cannot have an instance of that exact type. 显然,如果实例的类型完全是BaseClass ,则可以进行Visit(Base b)的无限递归调用,这是因为我认为BaseClass是抽象的:这样,您就不能拥有该类型的实例。


EDIT: 编辑:

I made some performance tests with this approximate load: 我用这种近似的负载进行了一些性能测试:

  1. A BaseClass and 17 DerivedClasses implementing an IVisitable interface; 实现IVisitable接口的BaseClass和17 DerivedClasses
  2. a list with a milion instances of BaseClass to visit; 包含数百个BaseClass实例的列表;
  3. a ClassicVisitor and a DynamicVisitor both implementing an IVisitor interface; ClassicVisitorDynamicVisitor都实现了IVisitor接口;
  4. All the Accept methods call only the Visit method and all the Visit methods are empty, to maximize the measured difference in time. 所有的Accept方法仅调用Visit方法,而所有Visit方法都是空的,以最大程度地测量时间差。
  5. Time measured with a Stopwatch in both Debug and Release mode. 在调试和释放模式下使用Stopwatch测量的时间。

Result: the DynamicVisitor is more than 10 times slower (~5/600ms against ~50ms). 结果: DynamicVisitor速度慢了10倍以上 (〜5 / 600ms对50ms)。

So, this solution is suitable for contexts in which you have a small amount of calls to make on the visitor. 因此,此解决方案适用于您需要对访问者进行少量调用的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM