如何评估ExpressionVisitor中的表达式?
我需要在执行它之前使用ExpressionVisitor来分析表达式。为了我的需要,我需要评估分数表达的正确部分,但我不知道如何去做。下面是我有一个示例代码:如何评估ExpressionVisitor中的表达式?
internal class RulesChecker : ExpressionVisitor
{
private readonly object data;
public RulesChecker(object data)
{
this.data = data;
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.Divide)
{
var rightExpression = node.Right;
// compile the right expression and get his value
}
return base.VisitBinary(node);
}
}
假设我有这样的代码来评估:
Expression<Func<DataInfo, decimal?>> expression = x => x.A/(x.B + x.C);
var rulesChecker = new RulesChecker(data);
rulesChecker.Visit(expression);
在VisitBinary功能,我会收到包含左侧和右侧部分节点分割操作。我的问题是,我如何评估我将在操作的正确部分获得的价值?
通常,您可以使用此方法来评估lambda表达式(并通过):
protected object EvaluateExpression(Expression expression)
{
var lambda = Expression.Lambda(expression);
var compiled = lambda.Compile();
var value = compiled.DynamicInvoke(null);
return value;
}
然而,在你的情况下,这是行不通的,因为你试图评估表达依赖于x
,除非你按照Wiktor的建议为它指定具体的值,否则无法评估。
为了给该参数指定的值,则需要修改的方法,例如:
protected static object EvaluateExpression(Expression expression, ParameterExpression parameterX)
{
var lambda = Expression.Lambda(expression, parameterX);
var compiled = lambda.Compile();
return compiled.DynamicInvoke(5);
// 5 here is the actual parameter value. change it to whatever you wish
}
此版本的方法,但是,必须作为一个参数,它表示x
的ExpressionParameter对象在你的表情中,以便它知道如何处理传递给DynamicInvoke()
的值。
为了获得合适的ExpressionParameter
对象,您需要访问根表达式,而不是其中的一个节点,所以我想在访问者中执行它会很尴尬。
如果我正确理解了你的话,你想返回你的表达式的结果是一个修改过的表达式树,其右边的分区以某种方式被评估。你会使用BinaryExpression
的Update
方法与你的价值来代替正确的节点:
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.Divide)
{
var rightExpression = node.Right;
// compile the right expression and get his value
var newRightExpression = Evaluate(rightExpression);
return node.Update(node.Left, node.Conversion, newRightExpression);
}
return base.VisitBinary(node);
}
在这段代码,newRightExpression
需要是从Expression
继承的类型。如果有合适的节点计算,比方说,一个double
值,那么你就需要将其包装在一个ConstantExpression
:
double rightValue = EvaluateToDouble(rightExpression);
var newRightExpression = Expression.Constant(rightValue, typeof(double));
我觉得这个问题最难的部分是处理的变量。所以我会开始通过替换常量的变量。之后你只需要执行和更新表达式。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
var value1 = 1;
var value2 = 2;
var value3 = new { MyValue = 3 };
var data = new DataInfo { A = 10, B = 1, C = -1 };
Expression<Func<DataInfo, decimal?>> expression = x => x.A/(x.B + x.C) + (value1 + value2) + value3.MyValue;
// create a list of variables that will be used when evaluating the expression
var variables = new Dictionary<Type, object>();
// add the root object
variables.Add(data.GetType(), data);
// find variables that are referenced in the expression
var finder = new VariablesFinder(variables);
finder.Visit(expression);
// replace variables with ConstantExpressions
var visitor = new VariableReplacer(variables);
var newExpression = visitor.Visit(expression);
var rulesChecker = new RulesChecker();
var checkedExpression = rulesChecker.Visit(newExpression);
}
}
internal class RulesChecker : ExpressionVisitor
{
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.Divide)
{
var rightBinaryExpression = node.Right as BinaryExpression;
if (rightBinaryExpression != null)
{
node = node.Update(node.Left, node.Conversion, this.Execute(rightBinaryExpression));
}
}
return base.VisitBinary(node);
}
private Expression Execute(BinaryExpression node)
{
var lambda = Expression.Lambda(node);
dynamic func = lambda.Compile();
var result = func();
return Expression.Constant(result, result.GetType());
}
}
internal class VariableReplacer : ExpressionVisitor
{
private readonly Dictionary<Type, object> _variables;
public VariableReplacer(Dictionary<Type, object> variables)
{
this._variables = variables;
}
protected override Expression VisitMember(MemberExpression node)
{
return this.HandleProperty(node) ??
this.HandleField(node) ??
node;
}
private Expression HandleField(MemberExpression memberExpression)
{
var fieldInfo = memberExpression.Member as FieldInfo;
if (fieldInfo != null)
{
var value = fieldInfo.GetValue(this.GetVarialbe(fieldInfo));
return Expression.Constant(value, fieldInfo.FieldType);
}
return null;
}
private Expression HandleProperty(MemberExpression memberExpression)
{
var propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo != null)
{
var value = propertyInfo.GetValue(this.GetVarialbe(propertyInfo), null);
return Expression.Constant(value, propertyInfo.PropertyType);
}
return null;
}
private object GetVarialbe(MemberInfo memberInfo)
{
return this._variables[memberInfo.DeclaringType];
}
}
internal class VariablesFinder : ExpressionVisitor
{
private readonly Dictionary<Type, object> _variables;
public VariablesFinder(Dictionary<Type, object> variables)
{
this._variables = variables;
}
protected override Expression VisitConstant(ConstantExpression node)
{
this.AddVariable(node.Type, node.Value);
return base.VisitConstant(node);
}
private void AddVariable(Type type, object value)
{
if (type.IsPrimitive)
{
return;
}
if (this._variables.Keys.Contains(type))
{
return;
}
this._variables.Add(type, value);
var fields = type.GetFields().Where(x => !x.FieldType.IsPrimitive).ToList();
foreach (var field in fields)
{
this.AddVariable(field.FieldType, field.GetValue(value));
}
}
}
class DataInfo
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D;
}
}
我认为@ w0lf是在正确的路径上。
要从访问者中获取参数,您需要重写VisitLambda。最好的方法是覆盖访问者的所有可用方法,并将参数传递给所有方法。
另一种方法是保存最新的参数。实际上,参数数组在整个lambda表达式中都是相同的。
这是一段代码,将除法操作的右侧乘以2,并将其替换为原始表达式,假设右侧和左侧都是double类型。
class Program
{
static void Main(string[] args)
{
Expression<Func<DateTime, double>> abc = v => 1.0d * v.Ticks/(v.Month + v.Minute);
MyExpressionVisitor mev = new MyExpressionVisitor(DateTime.Now);
var ret = mev.Visit(abc);
}
}
internal class MyExpressionVisitor : ExpressionVisitor
{
IEnumerable<ParameterExpression> _parameters = null;
object _parameterValue = null;
public MyExpressionVisitor(object valueOfParameter)
{
_parameterValue = valueOfParameter;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = node.Parameters;
return base.VisitLambda<T>(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (_parameters != null)
{
// Evaluate right node.
var value = EvaluateExpression(node.Right, _parameters.ToArray(), _parameterValue);
// substitute value with 2 * value.
var newRight = Expression.Constant(value * 2);
var ret = node.Update(node.Left, node.Conversion, newRight);
return ret;
}
return base.VisitBinary(node);
}
protected double EvaluateExpression(Expression expression, ParameterExpression[] parameters, object parameterValue)
{
var lambda = Expression.Lambda(expression, parameters);
var compiled = lambda.Compile();
var value = compiled.DynamicInvoke(parameterValue);
return Convert.ToDouble(value);
}
}
“参数”是一个数组,但parameterValue是标量吗?如果lambda具有多个参数,这将引发异常。访问者构造函数应该可能接受一组参数值(可能用[params关键字](http://msdn.microsoft.com/en-us/library/w5zay9db.aspx)声明,以便它也可以在不明确的情况下被调用构建一个数组)。 – luksan 2012-03-04 01:28:21
在问题的例子中,表达式的数据类型是Expression
这种评估的预期结果是什么?你会用具体的价值取代你的术语还是什么? – 2012-01-06 21:24:32
增加一个简单的想法:递归案例:value = evaluate(node.left)+ node.value + evaluate(node.right)基本案例:if(isLeaf(node))return node.value; – Adrian 2012-01-06 21:32:58