C#ExpressionVisitor实施的动机是什么?

时间:2015-01-22 14:41:42

标签: c# design-patterns linq-expressions visitor-pattern

我必须为任务设计一个解决方案,我想使用理论上与C#的ExpressionVisitor类似的东西。

为了好奇,我打开了ExpressionVisitor的.NET源代码来查看它。从那时起,我一直在想为什么.NET团队实现了访问者。

例如MemberInitExpression.Accept如下所示:

protected internal override Expression Accept(ExpressionVisitor visitor) {
    return visitor.VisitMemberInit(this);
}

我的 - 可能是菜鸟 - 问题是:它有意义吗?我的意思是,接受方法本身不应该对它本身如何实现访问负责吗?我的意思是我预计这样的事情(将internal可见性从外部移除可覆盖):

protected override Expression Accept(ExpressionVisitor visitor) {
    return this.Update(
            visitor.VisitAndConvert(this.NewExpression, "VisitMemberInit"),
            visitor.Visit(this.Bindings, VisitMemberBinding)
            );
}

但是此代码位于基础ExpressionVisitor的{​​{1}}方法中,该方法从VisitMemberInit调用。因此,MemberInitExpression.Accept实现似乎没有任何好处。

为什么不只处理基础Accept中的树,而忘记所有ExpressionVisitor方法?

我希望你理解我的观点,并希望有人能够了解这一实施背后的动机。可能我根本不了解访客模式?...

2 个答案:

答案 0 :(得分:3)

访问者可以覆盖访问任何表达式的方式。如果您的提案已在所有地方实施,则永远不会调用访问者。所有访问逻辑都将在Accept的覆盖范围内。非BCL代码不能覆盖此方法。

如果您写visitor.Visit((Expression)this.SomeExpression)(就像您在问题中所做的那样)那么您将如何对SomeExpression类型执行动态调度?现在访问者必须执行动态调度。请注意,您的第二个代码段简化了假设,即要访问的所有子表达式都具有已知类型。尝试编写BinaryExpression的代码,看看我的意思。

也许我不明白,但这个建议没有意义。

Accept方法的目的是进行性能优化。每个接受都是虚拟呼叫,相当便宜。另一种方法是在访问者中使用表达式类型(这是一个枚举)进行大量切换。那可能比较慢。

答案 1 :(得分:2)

访问者模式允许算法基本上与其操作的结构分开。在这种情况下,它操作的结构是表达式树。

请注意,访问者中的Accept方法为virtual。这意味着我们可以想象编写ExpressionVisitor的不同实现,它们对表达式树执行不同的操作(实际上,有不同的实现)。我们可以在不更改表达式树类本身的任何代码的情况下执行此操作。

不同访问者实现的示例可能类似于让一个访问者将表达式树转换回表示C#代码的字符串(或者可能是另一种语言的代码)。