使用查找派生示例的C#中的表达式树(表达式树访问者与模式匹配)

美好的一天。表达式树,尤其是与Visitor模式结合使用时,一直是一个令人困惑的话题。因此,有关此主题的信息越丰富,示例越多,那些有兴趣的人将更容易找到对他们而言清晰而有用的内容。



这篇文章照常构建-从概念框架和定义开始,以示例和使用方式结束。目录如下。

表达式树的基本知识表达式树的
语法表达式
类型
模式匹配
天真的来访者
经典来访者

嗯,目标不是强加特定的解决方案,也不是说一种解决方案比另一种更好。考虑到您案件中的所有细微差别,我建议自己得出结论。我将以我的榜样发表意见。


表达树


基础


首先,您需要处理表达式树。它们表示Expression的类型或其任何继承人(稍后将讨论它们)。在通常情况下,表达式/算法以可执行代码/指令的形式呈现,而用户可能没有太多要做(主要是执行)。 Expression类型允许您将表达式/算法(通常为lambda,但不是必需的)表示为以用户可以访问的树结构组织的数据。以树状方式组织有关算法和类名称的信息的方式为我们提供了“表达式树”。

为了清楚起见,我们将分析一个简单的示例。假设我们有lambda

(x)=> Console.WriteLine(x + 5)

这可以表示为以下树


树的根是MethodCall的顶部,方法的参数也是表达式,因此它可以具有任意数量的子代。

在我们的例子中,只有一个后代-“ ArithmeticOperation的峰值。它包含有关它是哪种操作的信息,左右操作数也是表达式。这样的顶点将始终具有2个后代。

操作数由常量(Constant)和参数(Parameter)表示。这样的表达没有后代。

这些是非常简化的示例,但充分体现了本质。

表达式树的主要特征是可以对其进行解析,并读取有关算法应执行的所有必要信息。从某些角度来看,这与属性相反。属性是对行为进行声明式描述的一种手段(非常有条件,但最终目标大致相同)。而表达式树正在使用函数/算法来描述数据。

例如,它们在实体框架提供程序中使用。该应用程序很明显-解析表达式树,了解在那里应该执行的操作,并根据此描述制作SQL。鲜为人知的例子是用于moking的moq。表达式树也用于DLR。(动态语言运行时)。编译器开发人员使用它们来确保动态特性和dotnet之间的兼容性,而不是生成MSIL

还值得一提的是,表达式树是不可变的。

句法


接下来值得讨论的是语法。有两种主要方法:

  • 通过Expression类的静态方法创建表达式树
  • 使用在Expression中编译的Lambda表达式

表达式类的静态方法


通过Expression类的静态方法创建表达式树的使用较少(尤其是从用户的角度来看)。这很麻烦,但很简单,我们有
许多基本的积木可供使用,您可以用它们来构建相当复杂的
东西。创建是通过静态方法进行的,因为表达式构造器有一个内部修饰符。但这并不意味着您需要发现反射。

例如,我将从上面的示例创建一个表达式:

(x)=> Console.WriteLine(x + 5)

ParameterExpression parameter = Expression.Parameter(typeof(double));
ConstantExpression constant = Expression.Constant(5d, typeof(double));
BinaryExpression add = Expression.Add(parameter, constant);
MethodInfo writeLine = typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(double) });
MethodCallExpression methodCall = Expression.Call(null, writeLine, add);
Expression<Action<double>> expressionlambda = Expression.Lambda<Action<double>>(methodCall, parameter);
Action<double> delegateLambda = expressionlambda.Compile();
delegateLambda(123321);

这可能不是很方便的方法,但是它完全反映了表达式树的内部结构。另外,此方法提供了更多的功能和可在表达式树中使用的功能:从循环,条件,try-catch,goto,赋值,以故障块结尾,调试信息,断点,动态等。

Lambda表达式


使用lambda作为表达式是一种更常见的方法。它的工作非常简单-编译阶段的智能编译器查看lambda的用途。并将其编译为委托或表达式。在一个已经掌握的示例中,它看起来如下

Expression<Action<double>> write =  => Console.WriteLine( + 5);

值得澄清这样的事情-表达式是详尽的描述。这足以
获得结果。诸如LambdaExpression或其后代之类的表达式树可以
转换为可执行IL。其余类型不能直接转换为可执行代码(但这没有多大意义)。
顺便说一句,如果有人批评快速编译表达式,可以看看这个第三方项目。

相反,在一般情况下是不正确的。代表不能只是拿起它并以表达方式介绍自己(但这仍然是可能的)。

并非所有的lambda都可以转换为表达式树。这些包括:

  • 包含赋值运算符
  • 动态贡献
  • 异步
  • 带身体(大括号)

double variable;
dynamic dynamic;
Expression<Action> assignment = () => variable = 5; //Compiler error: An expression tree may not contain an assignment operator
Expression<Func<double>> dynamically = () => dynamic; //Compiler error: An expression tree may not contain a dynamic operation
Expression<Func<Task>> asynchon = async () => await Task.CompletedTask; //Compiler error: Async lambda cannot be converted to expresiion trees
Expression<Action> body = () => { }; //Compiler error: A lambda expression with a statement body cannot be converted to an expression tree


表达式类型


我建议快速浏览可用的类型,以表示我们拥有的机会。所有这些都位于System.Linq.Expressions命名空间中,

我建议您先熟悉一些真正有趣且不寻常的功能。我将在平板电脑中放在一起的简单类型的表达式进行简要说明。

动态


使用DynamicExpression,可以在表达式树中使用动态及其所有功能。有一个相当混乱的API,我在本示例中使用的时间比在所有其他示例中使用的时间更长。所有的困惑都是由一堆各种各样的标志提供的。其中一些与您要寻找的类似,但不一定。当使用动态表达式树时,很难说出一个错误。例:

var parameter1 = Expression.Parameter(typeof(object), "name1");
var parameter2 = Expression.Parameter(typeof(object), "name2"); 
var dynamicParam1 = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
var dynamicParam2 = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
CallSiteBinder csb = Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(CSharpBinderFlags.None, ExpressionType.Add, typeof(Program), new[] { dynamicParam1, dynamicParam2 });
var dyno = Expression.Dynamic(csb, typeof(object), parameter1, parameter2);
Expression<Func<dynamic, dynamic, dynamic>> expr = Expression.Lambda<Func<dynamic, dynamic, dynamic>>(dyno, new[] { parameter1, parameter2 });
Func<dynamic, dynamic, dynamic> action = expr.Compile();
var res = action("1", "2");
Console.WriteLine(res); //12
res = action(1, 2);
Console.WriteLine(res); //3

我明确指出了活页夹的来源,以避免与System.Reflection中的活页夹混淆。有趣的是,我们可以执行ref和out参数,命名参数,一元运算,并且原则上可以通过动态完成所有操作,但这需要一些技巧。

异常捕获块


我要注意的第二件事是try / catch / finally / fault功能,或者说,
我们可以访问Fault块。它在C#中不可用,但在MSIL中可用,这是一种
最终模拟,将在发生任何异常的情况下执行。在下面的示例中,将引发异常,此后将显示“ Hi”,程序将等待输入。只有这样,它才会完全掉落。我不建议您使用这种做法。

var throwSmth = Expression.Throw(Expression.Constant(new Exception(), typeof(Exception)));
var log = Expression.Call(null, typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(string) }), Expression.Constant("Hi", typeof(string)));
var read = Expression.Call(null, typeof(Console).GetMethod(nameof(Console.ReadLine)));
var fault = Expression.TryFault(throwSmth, Expression.Block(new[] { log, read }));
Expression<Action> expr = Expression.Lambda<Action>(fault);
Action compiledExpression = expr.Compile();
compiledExpression();

可用的表达式树类型的简要说明


一种简短的介绍
主要的
表达, . ,
Expression<TDelegate>
BinaryExpression(+, — )
UnaryExpression(+, -), throw
ConstantExpression
ParameterExpression
MethodCallExpression, MethodInfo
IndexExpression
BlockExpression, .
ConditionalExpression— if-else
LabelTargetgoto
LabelExpression, . LabelTarget. , GotoExpression, — . void, .
GotoExpression. . ( .. «break»)
LoopExpression, «break»
SwitchCaseSwitchExpression
SwitchExpressionswitch/case
TryExpressiontry/catch/finally/fault
CatchBlock, ,
ElementInitIEnumerable. ListInitExpression
ListInitExpression+
DefaultExpression
NewArrayExpression+
NewExpression
/
MemberAssignment
MemberBinding, , ,
MemberExpression/
MemberInitExpression
MemberListBinding/
MemberMemberBinding/ , /
LambdaExpression
InvocationExpression-
DebugInfoExpression.
SymbolDocumentInfo, .
DynamicExpression( )
RuntimeVariablesExpression/
TypeBinaryExpression, (is)



  • ExpressionVisitor — . .
  • DynamicExpressionVisitor — DynamicExpression ( VisitDynamic)


此信息足以开始比较使用表达式树的方法。我决定以找到导数的例子来解析所有这一切。我没有预见所有可能的选择,只有基本的选择。但是,如果由于某种原因有人决定修改和使用它,我将很高兴通过对我的存储库的请求分享改进


模式匹配


因此,任务是对微分进行演算。您可以估算以下内容:对于不同类型的运算-乘法,除法等,有一些规则可以找到导数。根据操作,您必须选择一个特定的公式。在这种平庸的表述中,理想地将任务放在开关/盒上在最新版本的语言中,我们看到了switch / case 2.0或模式匹配

这里很难讨论一些事情。在集线器上,如此大量的代码看起来很繁琐且阅读效果很差,所以我建议您看一下github用一个导数的例子,结果像这样

    public class PatterntMatchingDerivative
    {
        private readonly MethodInfo _pow = typeof(Math).GetMethod(nameof(Math.Pow));
        private readonly MethodInfo _log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });
        private readonly ConstantExpression _zero = Expression.Constant(0d, typeof(double));
        private readonly ConstantExpression _one = Expression.Constant(1d, typeof(double));
		
        public Expression<Func<double, double>> ParseDerivative(Expression<Func<double, double>> function)
        {
            return Expression.Lambda<Func<double, double>>(ParseDerivative(function.Body), function.Parameters);
        }

        private Expression ParseDerivative(Expression function) => function switch
        {
            BinaryExpression binaryExpr => function.NodeType switch
            {
                ExpressionType.Add => Expression.Add(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),
                ExpressionType.Subtract => Expression.Subtract(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),

                ExpressionType.Multiply => (binaryExpr.Left, binaryExpr.Right) switch
		{	
	  	    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression _) => constant,
		    (ParameterExpression _, ConstantExpression constant) => constant,
		    _ => Expression.Add(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right)))
		},

                ExpressionType.Divide => (binaryExpr.Left, binaryExpr.Right) switch
		{
		    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression parameter) => Expression.Divide(constant, Expression.Multiply(parameter, parameter)),
		    (ParameterExpression _, ConstantExpression constant) => Expression.Divide(_one, constant),
		    _ => Expression.Divide(Expression.Subtract(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right))
	        },
            },
            MethodCallExpression methodCall when methodCall.Method == _pow => (methodCall.Arguments[0], methodCall.Arguments[1]) switch
            {
                (ConstantExpression constant, ParameterExpression _) => Expression.Multiply(methodCall, Expression.Call(null, _log, constant)),
                (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, _pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))),
                (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(ParseDerivative(expression), methodCall), Expression.Call(null, _log, constant)),
             },
             _ => function.NodeType switch
            {
                ExpressionType.Constant => _zero,
                ExpressionType.Parameter => _one,
                _ => throw new OutOfMemoryException("Bitmap best practice")
             }
        };
    }


看起来有点不寻常,但很有趣。很高兴写这篇文章-所有条件有机地结合在一起。

该示例说明了一切,您无法用文字更好地描述它。

天真的访客


在这样的任务中,表情树访客立即浮现在脑海,这
在粉丝中引起了很大的轰动,并引起了粉丝的恐慌,他们在厨房里讨论敏捷问题。“不要惧怕无知,而要知道虚假的知识。最好什么都不知道,而不是考虑真相,那是不正确的。” 记住托尔斯泰的这个美妙短语,承认无知并争取Google的支持,您可以找到以下指南

我拥有的链接是查询“表达式树访问者”的第一个链接(在1949年西伯利亚之后)。
乍一看,这正是我们所需要的。本文的标题符合我们的意愿,示例中的类均以后缀Visitor命名

在回顾了文章并进行了类推之后,通过类比我们的派生示例,我们得到:
链接到github

public class CustomDerivativeExpressionTreeVisitor
    {
        public Expression<Func<double, double>> Visit(Expression<Func<double, double>> function)
        {
            return Expression.Lambda<Func<double, double>>(Visitor.CreateFromExpression(function.Body).Visit(), function.Parameters);
        }
    }

    public abstract class Visitor
    {
        protected static readonly MethodInfo Pow = typeof(Math).GetMethod(nameof(Math.Pow));
        protected static readonly MethodInfo Log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });
        protected readonly ConstantExpression Zero = Expression.Constant(0d, typeof(double));
        protected readonly ConstantExpression One = Expression.Constant(1d, typeof(double));
        public abstract Expression Visit();
        public static Visitor CreateFromExpression(Expression node)
            => node switch
            {
                BinaryExpression be => new BinaryVisitor(be),
                MethodCallExpression mce when mce.Method == Pow => new PowMethodCallVisitor(mce),
                _ => new SimpleVisitor(node),
            };
        
    }

    public class BinaryVisitor : Visitor
    {
        private readonly BinaryExpression _node;
        
        public BinaryVisitor(BinaryExpression node)
        {
            _node = node;
        }

        public override Expression Visit()
            => _node.NodeType switch
            {
                ExpressionType.Add => Expression.Add(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),
                ExpressionType.Subtract => Expression.Subtract(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)),

                ExpressionType.Multiply => (binaryExpr.Left, binaryExpr.Right) switch
		{	
	  	    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression _) => constant,
		    (ParameterExpression _, ConstantExpression constant) => constant,
		    _ => Expression.Add(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right)))
		},

                ExpressionType.Divide => (binaryExpr.Left, binaryExpr.Right) switch
		{
		    (ConstantExpression _, ConstantExpression _) => _zero,
		    (ConstantExpression constant, ParameterExpression parameter) => Expression.Divide(constant, Expression.Multiply(parameter, parameter)),
		    (ParameterExpression _, ConstantExpression constant) => Expression.Divide(_one, constant),
		    _ => Expression.Divide(Expression.Subtract(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right))
	        },
            };
    }

    public class PowMethodCallVisitor : Visitor
    {
        private readonly MethodCallExpression _node;

        public PowMethodCallVisitor(MethodCallExpression node)
        {
            _node = node;
        }

        public override Expression Visit()
            => (_node.Arguments[0], _node.Arguments[1]) switch
            {
                (ConstantExpression constant, ParameterExpression _) => Expression.Multiply(_node, Expression.Call(null, Log, constant)),
                (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, Pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))),
                (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(CreateFromExpression(expression).Visit(), _node), Expression.Call(null, Log, constant)),
            };
    }

    public class SimpleVisitor : Visitor
    {
        private readonly Expression _node;

        public SimpleVisitor(Expression node)
        {
            _node = node;
        }

        public override Expression Visit()
            => _node.NodeType switch
            {
                ExpressionType.Constant => Zero,
                ExpressionType.Parameter => One,
            };
    }


实际上-我们将开关案例分散到不同的类别。他们不少,魔法没有出现。所有相同的情况,更多行。承诺的双重派遣在哪里?

经典访客和双重调度


在这里,有必要介绍Visitor模板本身,它也是Visitor,这是Expression tree visitor的基础。让我们仅以表达式树为例进行分析。

第二,假设我们设计了表达式树。我们希望使用户能够遍历表达式树,并根据节点的类型(表达式类型)执行某些操作。

第一种选择是什么也不做。也就是说,强迫用户使用开关/外壳。这不是一个坏选择。但是这里有一个细微差别:我们散布负责特定类型的逻辑。简而言之,多态性和虚拟挑战(也称为后期绑定)),可以将类型定义移至运行时,并从我们的代码中删除这些检查。对于我们而言,具有创建所需类型的实例的逻辑就足够了,然后一切将由运行时为我们完成。

第二种选择。显而易见的解决方案是将逻辑引入虚拟方法。通过覆盖每个后继对象中的虚拟方法,我们可以忽略开关/大小写。多态调用的机制将为我们决定。方法表将在此处工作,方法将通过其中的偏移量进行调用。但这是整篇文章的主题,所以我们不要着迷。虚拟方法似乎可以解决我们的问题。但是不幸的是,他们创造了另一个。对于我们的任务,我们可以添加GetDeriviative()方法。但是现在表达式类本身看起来很奇怪。我们可以在所有场合添加此类方法,但它们不适合该类的一般逻辑。而且,我们仍然没有提供进行类似于用户的操作的机会(当然是通过适当的方式)。我们需要让用户为每种特定类型定义逻辑,但保留多态性(我们可以使用)。

仅用户为此付出的努力不会成功。

这才是真正的访客所在。在层次结构的基本类型(本例中为Expression)中,我们定义了以下形式的方法

virtual Expression Accept(ExpressionVisitor visitor);

在继承人中,此方法将被覆盖。

ExpressionVisitor本身是一个基类,其中包含
对每种层次结构类型都具有相同签名的虚拟方法以ExpressionVisitor类为例,VisitBinary(...),VisitMethodCall(...),VisitConstant(...),VisitParameter(...)。

这些方法在层次结构的相应类中调用。

那些。BinaryExpression类中的Accept方法将如下所示:

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

结果,为了定义新的行为,用户只需要创建ExpressionVisitor类的继承人,即可在其中重新定义用于解决一个问题的相应方法。在我们的例子中,创建了DerivativeExpressionVisitor。

此外,我们有一些Expression的后继对象,但哪些是未知的,但不是必需的。
我们使用所需的ExpressionVisitor实现调用虚拟的Accept方法,即与DerivativeExpressionVisitor一起使用。由于采用了动态调度,因此调用了Accept的重写实现,例如运行时,例如BinaryExpression。在此方法的主体中,我们完全理解我们处于BinaryExpression中,但是我们不知道遇到了哪个ExpressionVisitor继承者。但是由于VisitBinary也是虚拟的,我们不需要知道。同样,我们只是通过引用基类来调用,该调用是动态(在运行时)调度的,并且调用了运行时类型的重写VisitBinary实现。对于双重派遣而言,如此之多-乒乓球的风格就是“你做”-“不,你”。

它给我们带来了什么。实际上,这使得可以从外部“添加”虚拟方法,而不是
换课。听起来不错,但也有缺点:

  1. 以“接受”方法的形式出现的一些左派分子,它同时负责所有事务
  2. 一个好的哈希函数的连锁反应是,当您仅向层次结构中添加一个继承人时,在最坏的情况下,每个人都必须完成访问者的操作

但是由于使用表达式的特殊性,表达式树的性质允许这些成本,因为这种解决方法是其主要功能之一。
在这里,您可以看到所有可用于重载的方法。

因此,让我们看看它的外观。
链接到github。

public class BuildinExpressionTreeVisitor : ExpressionVisitor
    {
        private readonly MethodInfo _pow = typeof(Math).GetMethod(nameof(Math.Pow));
        private readonly MethodInfo _log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });
        private readonly ConstantExpression _zero = Expression.Constant(0d, typeof(double));
        private readonly ConstantExpression _one = Expression.Constant(1d, typeof(double));

        public Expression<Func<double, double>> GetDerivative(Expression<Func<double, double>> function)
        {
            return Expression.Lambda<Func<double, double>>(Visit(function.Body), function.Parameters);
        }

        protected override Expression VisitBinary(BinaryExpression binaryExpr)
            => binaryExpr.NodeType switch
            {
                ExpressionType.Add => Expression.Add(Visit(binaryExpr.Left), Visit(binaryExpr.Right)),
                ExpressionType.Subtract => Expression.Subtract(Visit(binaryExpr.Left), Visit(binaryExpr.Right)),

                ExpressionType.Multiply when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ConstantExpression => _zero,
                ExpressionType.Multiply when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ParameterExpression => binaryExpr.Left,
                ExpressionType.Multiply when binaryExpr.Left is ParameterExpression && binaryExpr.Right is ConstantExpression => binaryExpr.Right,
                ExpressionType.Multiply => Expression.Add(Expression.Multiply(Visit(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, Visit(binaryExpr.Right))),

                ExpressionType.Divide when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ConstantExpression => _zero,
                ExpressionType.Divide when binaryExpr.Left is ConstantExpression && binaryExpr.Right is ParameterExpression => Expression.Divide(binaryExpr.Left, Expression.Multiply(binaryExpr.Right, binaryExpr.Right)),
                ExpressionType.Divide when binaryExpr.Left is ParameterExpression && binaryExpr.Right is ConstantExpression => Expression.Divide(_one, binaryExpr.Right),
                ExpressionType.Divide => Expression.Divide(Expression.Subtract(Expression.Multiply(Visit(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, Visit(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right)),
            };

        protected override Expression VisitMethodCall(MethodCallExpression methodCall)
            => (methodCall.Arguments[0], methodCall.Arguments[1]) switch
            {
                (ConstantExpression constant, ParameterExpression _) => Expression.Multiply(methodCall, Expression.Call(null, _log, constant)),
                (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, _pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))),
                (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(Visit(expression), methodCall), Expression.Call(null, _log, constant)),
            };

        protected override Expression VisitConstant(ConstantExpression _) => _zero;

        protected override Expression VisitParameter(ParameterExpression b) => _one;
    }


发现


也许像大多数编程任务一样,不能给出明确的答案。一如既往,一切都取决于具体情况。我喜欢本例中的常规模式匹配,因为 我没有将其开发到工业发展的规模。如果此表达式将不受控制地增加,则值得考虑访客。甚至是天真的访问者都享有生命权-毕竟,如果层次结构本身没有提供支持,这也是将大量代码分散到类中的一种好方法。即使在这里也有例外。

同样,访问者对层次结构的支持也引起了很大争议。

但是我希望这里提供的信息足以做出正确的选择。

Source: https://habr.com/ru/post/undefined/


All Articles