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

What is the motivation of C# ExpressionVisitor's implementation?

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

出于好奇,我打开了 ExpressionVisitor 的 .NET 源代码进行了查看。从那时起,我一直想知道为什么 .NET 团队会像他们那样实现访问者。

例如 MemberInitExpression.Accept 看起来像这样:

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

我的——可能是菜鸟——的问题是:它有意义吗?我的意思是 Accept 方法本身不应该负责它如何在自身内部实现访问吗?我的意思是我已经预料到这样的事情(删除 internal 可见性以从外部覆盖):

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

但此代码位于基础 ExpressionVisitorVisitMemberInit 方法中,该方法从 MemberInitExpression.Accept 调用。所以这里的 Accept 实施似乎没有任何好处。

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

我希望您能理解我的观点,并希望有人能阐明这一实施背后的动机。大概是我完全不懂Visitor模式吧?...

访问者可以覆盖任何表达式的访问方式。如果你的建议在所有地方都得到实施,那么访客就永远不会被召唤。所有访问逻辑都在 Accept 的覆盖中。非 BCL 代码无法覆盖此方法。

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

可能是我没看懂,但是这个提议没有意义。

Accept方法的目的是性能优化。每个接受都是一个相当便宜的虚拟调用。另一种方法是在访问者中对表达式类型(它是一个枚举)进行巨大的切换。这可能更慢。

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

请注意访问者中的 Accept 方法是 virtual。这意味着我们可以想象地编写 ExpressionVisitor 的不同实现,对表达式树做不同的事情(而且,确实有不同的实现)。我们可以做到这一点,而无需更改表达式树中的任何代码 类 本身。

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