Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 检测表达式树--如何获得每个子树的计算结果?_C#_Logging_Expression Trees_Instrumentation - Fatal编程技术网

C# 检测表达式树--如何获得每个子树的计算结果?

C# 检测表达式树--如何获得每个子树的计算结果?,c#,logging,expression-trees,instrumentation,C#,Logging,Expression Trees,Instrumentation,我正在表达式树中做一些工作,这是一个规则引擎 当您在表达式树上调用ToString()时,会得到一段可爱的诊断文本: ((Param_0.Customer.LastName == "Doe") AndAlso ((Param_0.Customer.FirstName == "John") Or (Param_0.Customer.FirstName == "Jane"))) 我编写了这段代码,试图用一些日志功能包装表达式: public Expression With

我正在表达式树中做一些工作,这是一个规则引擎

当您在表达式树上调用ToString()时,会得到一段可爱的诊断文本:

 ((Param_0.Customer.LastName == "Doe") 
     AndAlso ((Param_0.Customer.FirstName == "John") 
     Or (Param_0.Customer.FirstName == "Jane")))
我编写了这段代码,试图用一些日志功能包装表达式:

public Expression WithLog(Expression exp)
{
    return Expression.Block(Expression.Call(
        typeof (Debug).GetMethod("Print",
            new Type [] { typeof(string) }),
            new [] { Expression.Call(Expression.Constant(exp),
            exp.GetType().GetMethod("ToString")) } ), exp);
}
这应该允许我在表达式树中的不同位置插入日志记录,并在表达式树执行时获得中间的ToString()结果

我还没有完全弄清楚的是如何获得每个子表达式的计算结果并将其包含在日志输出中。理想情况下,出于诊断和审核目的,我希望看到如下输出:

Executing Rule: (Param_0.Customer.LastName == "Doe") --> true
Executing Rule: (Param_0.Customer.FirstName == "John") --> true
Executing Rule: (Param_0.Customer.FirstName == "Jane") --> false
Executing Rule: (Param_0.Customer.FirstName == "John") Or (Param_0.Customer.FirstName == "Jane")) --> true
Executing Rule: (Param_0.Customer.LastName == "Doe") AndAlso ((Param_0.Customer.FirstName == "John") Or (Param_0.Customer.FirstName == "Jane")) --> true
我怀疑我要么需要使用ExpressionVisitor遍历树并向每个节点添加一些代码,要么遍历树并分别编译和执行每个子树,但我还没有完全弄清楚如何实现这一点


有什么建议吗?

不幸的是,我没有使用C#和表达式树的经验,但我对口译员略知一二

我假设表达式树是一种AST,其中每个树节点都是公共层次结构中的一个类。我还假设您通过应用解释器模式,通过
expr.explor(context)
方法或
ExpressionInterpreter
访问者来评估此AST

使用
explorate()
方法时 您需要引入一个新的表达式类型
LoggedExpression
,其语义如下:

  • 它包含一个表达式
  • 求值时,它求值子节点并打印出字符串化的子节点和结果:
如果您的语言比简单的布尔表达式更复杂,您可能希望在求值之前进行日志记录,以便轻松地调试挂起的问题等

然后,必须将表达式树转换为记录的表达式树。这可以通过方法
AsLoggedExpression()
轻松完成,该方法复制每个节点,但将其包装在日志表达式中:

class Or : Expression {
  private Expression left;
  private Expression right;
  ...
  public Expression AsLoggedExpression() {
    return new LoggedExpression(new Or(left.AsLoggedExpression(), right.AsLoggedExpression()));
  }
}
某些节点可能会返回自身不变,而不是添加日志记录,例如常量或日志记录表达式本身(因此,将日志记录添加到树将是一个幂等操作)

使用访客时 访问者中的每个
visit()
方法负责计算表达式树节点。给定您的主
ExpressionInterpreter
访问者,我们可以导出一个
LoggingExpressionInterpreter
,它为每个节点类型记录表达式并对其求值:

class LoggingExpressionInterpreter : ExpressionInterpreter {
  ...
  public bool Visit(Expression.Or ast) {
    bool result = base.Visit(ast);
    log("Executing rule: " + child + " --> " + result);
    return result;
  }
}
在这里,我们不能使用组合而不是继承,因为这会破坏递归日志记录。评估任何节点时,日志解释器也将用于所有子节点,这一点至关重要。如果取消继承,则
Visit()
AcceptVisitor()
方法将需要一个应应用于子节点的访问者显式参数


我非常喜欢基于访问者的方法,因为它不必修改表达式树,而且代码总量也会减少(我猜)。

虽然阿蒙的文章在理论上是正确的,但C#ExpressionTrees没有解释器(据我所知)。但是,有一个编译器,还有一个很好的抽象访问者,可以很好地实现这一目的

public class Program
{
    static void Main(string[] args)
    {

        Expression<Func<int, bool>> x = (i => i > 3 && i % 4 == 0);
        var visitor = new GetSubExpressionVisitor();
        var visited = (Expression<Func<int, bool>>)visitor.Visit(x);
        var func = visited.Compile();
        var result = func(4);
    }
}

public class GetSubExpressionVisitor : ExpressionVisitor
{
    private readonly List<ParameterExpression> _parameters = new List<ParameterExpression>();

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        _parameters.AddRange(node.Parameters);
        return base.VisitLambda(node);
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        switch (node.NodeType)
        {
            case ExpressionType.Modulo:
            case ExpressionType.Equal:
            case ExpressionType.GreaterThanOrEqual:
            case ExpressionType.LessThanOrEqual:
            case ExpressionType.NotEqual:
            case ExpressionType.GreaterThan:
            case ExpressionType.LessThan:
            case ExpressionType.And:
            case ExpressionType.AndAlso:
            case ExpressionType.Or:
            case ExpressionType.OrElse:
                return WithLog(node);
        }
        return base.VisitBinary(node);
    }

    public Expression WithLog(BinaryExpression exp)
    {
        return Expression.Block(
            Expression.Call(
                typeof(Debug).GetMethod("Print", new Type[] { typeof(string) }),
                new[] 
                { 
                    Expression.Call(
                        typeof(string).GetMethod("Format", new [] { typeof(string), typeof(object), typeof(object)}),
                        Expression.Constant("Executing Rule: {0} --> {1}"),
                        Expression.Call(Expression.Constant(exp), exp.GetType().GetMethod("ToString")),
                        Expression.Convert(
                            exp,
                            typeof(object)
                        )
                    )
                }
            ),
            base.VisitBinary(exp)
        );
    }
}

您需要使用表达式访问者。您需要编译作为lambda表达式的节点。然后可以将编译后的lambda作为委托执行。如果内存可用,则每个lambda节点上都有一个compile方法返回一个委托。您可以执行委托以获得结果。我不知道表达式树在C#中是一种特殊的东西,它们是编译的。这可能意味着您必须创建一个新的表达式树来添加跟踪。您显示的代码已经完成了一半,但实际上不会导致日志执行。相反,它似乎在评估某些选定的子表达式。哦,我明白他想做什么了。我会相应地更新。表达式树是C#中的一种东西,从C#3.0/.NET3.5开始。规范的情况类似于
objectSource.Where(o=>o.Id>10)
或类似的情况。如果
objectSource
是一个SQL表,则可以将表达式编译为类似
SELECT*FROM Objects WHERE id>10)
的内容。如果它是内存中的集合,你会使用C#编译器。哇,这是一个非常酷的语言特性。顺便说一句,我知道没有其他语言(除了Lisp、Perl6、Mathematica等)具有类似的功能,尽管它可以通过操作符重载进行部分模拟。谢谢你向我解释这件事!似乎我应该再次检查Linux上C#的状态。这与问题并不相关,但CoreFX同时包含一个编译器和一个表达式解释器。要使用解释器,请调用
Compile(true)
而不是
Compile()
public class Program
{
    static void Main(string[] args)
    {

        Expression<Func<int, bool>> x = (i => i > 3 && i % 4 == 0);
        var visitor = new GetSubExpressionVisitor();
        var visited = (Expression<Func<int, bool>>)visitor.Visit(x);
        var func = visited.Compile();
        var result = func(4);
    }
}

public class GetSubExpressionVisitor : ExpressionVisitor
{
    private readonly List<ParameterExpression> _parameters = new List<ParameterExpression>();

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        _parameters.AddRange(node.Parameters);
        return base.VisitLambda(node);
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        switch (node.NodeType)
        {
            case ExpressionType.Modulo:
            case ExpressionType.Equal:
            case ExpressionType.GreaterThanOrEqual:
            case ExpressionType.LessThanOrEqual:
            case ExpressionType.NotEqual:
            case ExpressionType.GreaterThan:
            case ExpressionType.LessThan:
            case ExpressionType.And:
            case ExpressionType.AndAlso:
            case ExpressionType.Or:
            case ExpressionType.OrElse:
                return WithLog(node);
        }
        return base.VisitBinary(node);
    }

    public Expression WithLog(BinaryExpression exp)
    {
        return Expression.Block(
            Expression.Call(
                typeof(Debug).GetMethod("Print", new Type[] { typeof(string) }),
                new[] 
                { 
                    Expression.Call(
                        typeof(string).GetMethod("Format", new [] { typeof(string), typeof(object), typeof(object)}),
                        Expression.Constant("Executing Rule: {0} --> {1}"),
                        Expression.Call(Expression.Constant(exp), exp.GetType().GetMethod("ToString")),
                        Expression.Convert(
                            exp,
                            typeof(object)
                        )
                    )
                }
            ),
            base.VisitBinary(exp)
        );
    }
}
Executing Rule: ((i > 3) AndAlso ((i % 4) == 0)) --> True
Executing Rule: (i > 3) --> True
Executing Rule: ((i % 4) == 0) --> True
Executing Rule: (i % 4) --> 0