I need to use ExpressionVisitor to analyse an Expression before executing it. For my needs, i need to evaluate the right part of a Divide expression but i don't know how to do it. Here's a sample code that i have:
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);
}
}
Suppose that i have this code to evaluate:
Expression<Func<DataInfo, decimal?>> expression = x => x.A / (x.B + x.C);
var rulesChecker = new RulesChecker(data);
rulesChecker.Visit(expression);
In the VisitBinary function, i will receive a node that will contain the left and right part of the divide operation. My question is, how can i evaluate the value that i will get in the right part of the operation?
I think the hardest part of this problem is dealing with the variables. So I would start by replacing the variables for constants. After that you just need to execute and update the Expression.
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;
}
}
Usually you could use this method to evaluate a lambda expression (and pass ):
protected object EvaluateExpression(Expression expression)
{
var lambda = Expression.Lambda(expression);
var compiled = lambda.Compile();
var value = compiled.DynamicInvoke(null);
return value;
}
However, in your case this won't work, because the expression you're trying to evaluate depends on x
, which cannot be evaluated, unless you specify a concrete value for it, as Wiktor suggested.
In order to specify a value for the parameter, you need to modify the method as such:
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
}
This version of the method, however, must take as a parameter the ExpressionParameter object that represents the x
in your expression in order for it to know what to do with the value passed to DynamicInvoke()
.
In order to obtain the appropriate ExpressionParameter
object you need access to the root expression, not to one of its nodes, so I guess it would be awkward to do it in a visitor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With