I'm doing some work in Expression Trees, a rules engine of sorts.
When you call ToString() on an Expression Tree, you get a lovely bit of diagnostic text:
((Param_0.Customer.LastName == "Doe")
AndAlso ((Param_0.Customer.FirstName == "John")
Or (Param_0.Customer.FirstName == "Jane")))
I wrote this bit of code, in an attempt to wrap the Expression with some logging capability:
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);
}
This should allow me to insert logging at various places within the expression tree and get intermediate ToString() results when the expression tree executes.
What I haven't quite figured out is how to get the computed result of each sub-expression and include it in the log output. Ideally, I would like to see output that looks something like this, for diagnostic and auditing purposes:
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
I suspect that I either need to walk the tree using ExpressionVisitor and add some code to each node, or walk the tree and compile and execute each subtree individually, but I haven't quite figured out how to make this work yet.
Any suggestions?
While amon's post is correct in theory, there isn't an interpreter (that I know of) for C# ExpressionTrees. However, there is a compiler, and there is a nice abstract visitor which would work well for this purpose.
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)
);
}
}
I'm not entirely sure how well this code will work if you have a nested lambda, but if you don't have such a thing, this should do it.
Incorporated WithLog
code. The code outputs the following:
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