Basically I've wrote my own parser and I'm parsing a string into and expression and then compiling and storing to be reused later on.
For (an odd) example the type of string I'm parsing is this:-
if #name == 'max' and #legs > 5 and #ears > 5 then shoot()
The hash parts in the string are telling my parser to look at the properties on the object of type T that I pass in, if not found to look in a Dictionary that also gets passed in as extra.
I parse up the string into parts create an expression and join the expressions using methods from the PredicateBuilder.
I get the left value from where ever it may be and then the right value and turn it into an int32 or a string based on if it's wrapped in single quotes.. for now, then pass both into Expression.Equals/Expression.GreaterThan etc. dependant on the operator.
So far this works fine for equals and strings but take for example #ears which isn't a property on the object but is in the dictionary I end up with "does string equal int32" and it throws an exception.
I need to be able to parse the string from the dictionary into and int32 and then try the equal/greater/less than method.
Hopefully someone could shed some light on this as I haven't found anything yet that will do it and its driving me mad.
Here is the CreateExpression method I'm using to create an expression from the string.
private Expression<Func<T, IDictionary<string, string>, bool>> CreateExpression<T>(string condition)
{
string[] parts = condition.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
{
var typeCache = _cache[typeof(T).FullName];
var param = Expression.Parameter(typeCache.T, "o");
var param2 = Expression.Parameter(typeof(IDictionary<string, string>), "d");
/* Convert right hand side into correct value type
*/
Expression right = null;
if (parts[2][0] == '\'' && parts[2][parts[2].Length - 1] == '\'')
right = Expression.Constant(parts[2].Trim(new char[] { '\'' }), typeof(string));
else
{
int x = 0;
right = (int.TryParse(parts[2].Trim(), out x))
? Expression.Constant(x)
: Expression.Constant(parts[2].Trim(new char[] { '\'' }), typeof(string));
}
/* Get the left hand side value from object T or IDictionary and then attempt to convert it
* into the right hand side value type
*/
Expression left = null;
var key = (parts[0][0] == '#') ? parts[0].TrimStart('#') : parts[0];
if (_cache[typeCache.T.FullName].Properties.Find(key, true) == null)
{
var m = typeof(ExpressionExtensions).GetMethod("GetValue");
left = Expression.Call(null, m, new Expression[] { param2, Expression.Constant(key) });
}
else
left = Expression.PropertyOrField(param, key);
/* Find the operator and return the correct expression
*/
if (parts[1] == "==")
{
return Expression.Lambda<Func<T, IDictionary<string, string>, bool>>(
Expression.Equal(left, right), new ParameterExpression[] { param, param2 });
}
else if (parts[1] == ">")
{
return Expression.Lambda<Func<T, IDictionary<string, string>, bool>>(
Expression.GreaterThan(left, right), new ParameterExpression[] { param, param2 });
}
}
return null;
}
And here is the method to parse the whole string into parts and build up the expression.
public Func<T, IDictionary<string, string>, bool> Parse<T>(string rule)
{
var type = typeof(T);
if (!_cache.ContainsKey(type.FullName))
_cache[type.FullName] = new TypeCacheItem()
{
T = type,
Properties = TypeDescriptor.GetProperties(type)
};
Expression<Func<T, IDictionary<string, string>, bool>> exp = null;
var actionIndex = rule.IndexOf("then");
if (rule.IndexOf("if") == 0 && actionIndex > 0)
{
var conditionStatement = rule.Substring(2, actionIndex - 2).Trim();
var actionStatement = rule.Substring(actionIndex + 4).Trim();
var startIndex = 0;
var endIndex = 0;
var conditionalOperator = "";
var lastConditionalOperator = "";
do
{
endIndex = FindConditionalOperator(conditionStatement, out conditionalOperator, startIndex);
var condition = (endIndex == -1) ? conditionStatement.Substring(startIndex) : conditionStatement.Substring(startIndex, endIndex - startIndex);
var x = CreateExpression<T>(condition.Trim());
if (x != null)
{
if (exp != null)
{
switch (lastConditionalOperator)
{
case "or":
exp = exp.Or<T>(x);
break;
case "and":
exp = exp.And<T>(x);
break;
default:
exp = x;
break;
}
}
else
exp = x;
}
lastConditionalOperator = conditionalOperator;
startIndex = endIndex + conditionalOperator.Length;
} while (endIndex > -1);
}
else
throw new ArgumentException("Rule must start with 'if' and contain 'then'.");
return (exp == null) ? null : exp.Compile();
}
My eyes are open to suggestions or advice on this but the idea of parsing that string as is to return true or false has to be,
EDIT:
I've now alter my method that retrieves the value from the dictionary to a generic one as Fahad suggested to this:-
public static T GetValue<T>(this IDictionary<string, string> dictionary, string key)
{
string x = string.Empty;
dictionary.TryGetValue(key, out x);
if (typeof(T) == typeof(int))
{
int i = 0;
if (int.TryParse(x, out i))
return (T)(i as object);
}
return default(T);
//throw new ArgumentException("Failed to convert dictionary value to type.");
}
And this works fine but I would rather return null than a default value for the type i've tried using Nullable ... where T : struct and returning null when not found or can't convert but I don't know how to use the null result in my expression.
I think you should redo your problem analysis and try another way. One way I would propose is to have a generic method that would give you the value from a IDictionary or IList etc., and use this method to wrap around your Expression's. The Expression would only want the "Type" of the object to be satisfied, if that is done correctly, then you could easily do it. If you know the "Type" then you can implement it with generic methods, otherwise, you could still use reflection and do generics.
So all you have to think is how to get the value from the dictionary or any other list through expressions.
Hope that helps.
-Fahad
You could use Expression.Call(null, typeof(int), "Parse", Type.EmptyTypes, yourTextExpression)
Related
In my project, I want the user to be able to supply an argument in the shape of: "Fieldname=Value"; I will then check the arguments supplied, and check if the Fieldname is a valid property in a Model; then, I need to have a Linq.Where() query that dynamically selects the required Fieldname for the filtering:
start.exe -Filter "TestField=ThisValue"
should translate to:
List<Mutations>.Where(s => s.{TestField} == "ThisValue" ).FirstOrDefault
the problem is that I do not know how I convert a string, or a name of a property, to the s.{TestField} part..
You can use the Reflection and Expression APIs for this. To start, I am going to assume that you are actually using properties and not fields (you are using properties, right?)
var type = typeof(Mutations);
var member = type.GetProperty("TestProperty");
if (member == null)
throw new Exception("property does not exist");
var p1 = Expression.Parameter(type, "s");
var equal = Expression.Equal(
Expression.Property(p1, member),
Expression.Constant("Test Value", member.PropertyType)
);
var lambda = Expression.Lambda<Func<Mutations, bool>>(equal, p1);
var result = list.AsQueryable().FirstOrDefault(lambda);
If you are actually using public fields (why?!) you can make the following modifications GetProperty->GetField, Expression.Property->Expression.Field and member.PropertyType->member.FieldType. Use caution though; some ORMs only work with properties and thus would reject the otherwise valid Expression.
We can take the above and turn it into a reusable, generic method that returns an Expression:
using System.Linq.Expressions;
public static class ExpressionHelpers {
public static Expression CreateWhere<T>(string propertyName, string targetValue) {
var type = typeof(T);
var member = type.GetProperty(propertyName) ?? throw new Exception("Property does not exist");
var p1 = Expression.Parameter(type, "s");
var equal = Expression.Equal(
Expression.Property(p1, member),
Expression.Constant(targetValue, member.PropertyType)
);
return Expression.Lambda<Func<T, bool>>(equal, p1);
}
}
Calling this method might look like:
public static void SomeMethod() {
var list = new List<Mutations> { /* ... */ };
Expression clause = ExpressionHelpers.CreateWhere<Mutations>("TestProperty", "TestValue");
var result = list.AsQueryable().FirstOrDefault(clause);
if (result != null)
Console.WriteLine("Result = {0}", result);
}
Note that this doesn't do any sort of validation of the data types--it assumes the property is the same type as the input, which is currently string. If you need to deal with numbers or dates or what have you, you'll need to switch on the data type and provide the appropriate parsed data to the constant:
public static Expression CreateWhere<T>(string propertyName, string targetValue) {
var type = typeof(T);
var member = type.GetProperty(propertyName) ?? throw new Exception("Property does not exist");
var propType = member.PropertyType;
if ((propType.IsClass && propType != typeof(string)) || propType.IsInterface)
throw new Exception("Interfaces and Class Types are not supported");
var p1 = Expression.Parameter(type, "s");
Expression target = null;
if (propType == typeof(string))
target = Expression.Constant(targetValue, typeof(string));
else if (propType == typeof(int) && int.TryParse(targetValue, out var intVal))
target = Expression.Constant(intVal, typeof(int));
else if (propType == typeof(long) && long.TryParse(targetValue, out var longVal))
target = Expression.Constant(longVal, typeof(long));
else if (propType == typeof(DateTime) && DateTime.TryParse(targetValue, out var dateVal))
target = Expression.Constant(dateVal, typeof(DateTime));
else
throw new Exception("Target property type is not supported or value could not be parsed");
var equal = Expression.Equal(
Expression.Property(p1, member),
target
);
return Expression.Lambda<Func<T, bool>>(equal, p1);
}
As you can see this starts to get fairly complex with the more types you want to support. Also note that if you are using this without an ORM (just LINQ on a list) you are probably going to want add some support for case-[in]sensitive string comparisons. That can be delegated to a string.Equals call that could look something like this:
bool ignoreCase = true; // maybe a method parameter?
var prop = Expression.Property(p1, member);
Expression equal = null;
if (propType != typeof(string))
{
equal = Expression.Equal(prop, target);
}
else
{
var compareType = ignoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
var compareConst = Expression.Constant(compareType, typeof(StringComparison));
equal = Expression.Call(
typeof(string),
nameof(string.Equals),
new[] { typeof(string), typeof(string), typeof(StringComparison) },
prop,
target,
compareConst
);
}
return Expression.Lambda<Func<T, bool>>(equal, p1);
Note that depending on their support this may or may not work with an ORM (and may not be necessary since many databases are insensitive comparisons by default). The above also does not handle Nullable<T> (ie. int?) which adds its own set of complexities.
Inside a function receiving an expression, how can I check if the expression is a member access lambda ?
bool F<TSrc, TVal>(TSrc src, Expression<Func<TSrc, TVal>> exp) {
bool isMememberAccess = ???
if (!isMememberAccess) return false;
...
return true;
}
so that:
var emp = new Employee();
var res = F(emp, x => x.FirstName); // returns true;
var org = new Organization();
var res = F(org, x => x.Sales.Manager.FirstName); // returns true;
int i=0;
var res = F(emp, x => i); // returns false - expression is not a member access
I tried to check for exp.Body.NodeType == ExpressionType.MemberAccess but that returns true in both cases.
Any help ?
Captured lambda variables are stored in a generated struct type, passed in as a static constant. Your expression argument x => i is roughly equivalent to;
public struct locals
{
public int i;
}
var localState = new locals { i = i };
Expression.Lambda<Func<Employee, int>>(
Expression.MakeMemberAccess(
Expression.Constant(localState, typeof(locals)),
typeof(locals).GetField(nameof(locals.i))
),
Expression.Parameter(typeof(Employee),"x")
);
As you can see, this also includes a member expression. What you want to prove, is that the member expression is based on the type of the expression parameter.
bool F<TSrc, TVal>(TSrc src, Expression<Func<TSrc, TVal>> exp) {
var isMememberAccess = exp.Body is MemberExpression member
&& member.Expression is ParameterExpression parameter
&& parameter.Type == typeof(TSrc);
...
Is it possible to convert string
"value > 5 && value <= 10"
to if statement?
if (value > 5 && value <= 10)
{
//do something
}
I have conditions stored as strings in DB, so it must be a dynamic conversion
Instead you can treat it as a javascript line and can get this done using Windows Script Engines, provided value is a real value instead of variable name.
if(ScriptEngine.Eval("jscript", "value > 5 && value <= 10"))
{
//Your logic
}
Or if its a variable then you can build a JS function like below to accomplish this:
using (ScriptEngine engine = new ScriptEngine("jscript"))
{
string JSfunction = "MyFunc(value){return " + "value > 5 && value <= 10" + "}";
ParsedScript parsed = engine.Parse(JSfunction);
if(parsed.CallMethod("MyFunc", 3))
{
// Your Logic
}
}
Use Compute method of System.Data.DataTable:
static class ExpressionHelper
{
private static readonly DataTable dt = new DataTable();
private static readonly Dictionary<string, string> expressionCache = new Dictionary<string, string>();
private static readonly Dictionary<string, object> resultCache = new Dictionary<string, object>();
// to be amended with necessary transforms
private static readonly (string old, string #new)[] tokens = new[] { ("&&", "AND"), ("||", "OR") };
public static T Compute<T>(this string expression, params (string name, object value)[] arguments) =>
(T)Convert.ChangeType(expression.Transform().GetResult(arguments), typeof(T));
private static object GetResult(this string expression, params (string name, object value)[] arguments)
{
foreach (var arg in arguments)
expression = expression.Replace(arg.name, arg.value.ToString());
if (resultCache.TryGetValue(expression, out var result))
return result;
return resultCache[expression] = dt.Compute(expression, string.Empty);
}
private static string Transform(this string expression)
{
if (expressionCache.TryGetValue(expression, out var result))
return result;
result = expression;
foreach (var t in tokens)
result = result.Replace(t.old, t.#new);
return expressionCache[expression] = result;
}
}
Usage
var expr = "value > 5 && value <= 10";
var a1 = expr.Compute<bool>(("value", 5)); // false
var a2 = expr.Compute<bool>(("value", 7)); // true
var a3 = expr.Compute<bool>(("value", 11)); // false
You could use Linq.Expression to build up the expression tree, the one you provided:
"value > 5 && value <= 10"
var val = Expression.Parameter(typeof(int), "x");
var body = Expression.And(
Expression.MakeBinary(ExpressionType.GreaterThan, val, Expression.Constant(5)),
Expression.MakeBinary(ExpressionType.LessThanOrEqual, val, Expression.Constant(10)));
var lambda = Expression.Lambda<Func<int, bool>>(exp, val);
bool b = lambda.Compile().Invoke(6); //true
bool b = lambda.Compile().Invoke(11); //false
This is just an example to get some idea, still you need a smart way to parse and build the tree.
I am afraid that you will have to create the simple parser for that.
You can try use something like FParsec. This is an F# parser library. I am not aware of such code in C#
I want to pass the name of a property of a model to a method. Instead of using the name as string, I am using lambda expression as it is easy to make a typo, and also property names may be changed. Now if the property is a simple property (e.g: model.Name) I can get the name from the expression. But if it is a nested property (e.g: model.AnotherModel.Name) then how can I get full text ("AnotherModel.Name") from the expression. For example, I have the following classes:
public class BaseModel
{
public ChildModel Child { get; set; }
public List<ChildModel> ChildList { get; set; }
public BaseModel()
{
Child = new ChildModel();
ChildList = new List<ChildModel>();
}
}
public class ChildModel
{
public string Name { get;set; }
}
public void GetExpressionText<T>(Expression<Func<T, object>> expression)
{
string expText;
//what to do??
return expText;
}
GetExpressionText<BaseModel>(b => b.Child); //should return "Child"
GetExpressionText<BaseModel>(b => b.Child.Name); //should return "Child.Name"
GetExpressionText<BaseModel>(b => b.ChildList[0].Name); //should return "ChildList[0].Name"
My first thought was to use expression.Body.ToString() and tweak that a bit, but you would still need to deal with Unary (convert) etc. Assuming this is for logging and you want more control, the below can be used for formatting as wanted (e.g. if you want Child->Name for display purposes, string.Join("->",..) can be used). It may not be complete, but should you find any unsupported types, they should be easy to add.
PS: this post was generated before the question was closed. Just noticed it was reopend and submitting it now, but I haven't checked if particulars have been changed.
public string GetName(Expression e, out Expression parent)
{
if(e is MemberExpression m){ //property or field
parent = m.Expression;
return m.Member.Name;
}
else if(e is MethodCallExpression mc){
string args = string.Join(",", mc.Arguments.SelectMany(GetExpressionParts));
if(mc.Method.IsSpecialName){ //for indexers, not sure this is a safe check...
return $"{GetName(mc.Object, out parent)}[{args}]";
}
else{ //other method calls
parent = mc.Object;
return $"{mc.Method.Name}({args})";
}
}
else if(e is ConstantExpression c){ //constant value
parent = null;
return c.Value?.ToString() ?? "null";
}
else if(e is UnaryExpression u){ //convert
parent= u.Operand;
return null;
}
else{
parent =null;
return e.ToString();
}
}
public IEnumerable<string> GetExpressionParts(Expression e){
var list = new List<string>();
while(e!=null && !(e is ParameterExpression)){
var name = GetName(e,out e);
if(name!=null)list.Add(name);
}
list.Reverse();
return list;
}
public string GetExpressionText<T>(Expression<Func<T, object>> expression) => string.Join(".", GetExpressionParts(expression.Body));
You could use the C# 6.0 feature: nameof(b.Child) "Used to obtain the simple (unqualified) string name of a variable, type, or member."
which will also change on renaming. But this will only return the propertyname and not the complete path. Returning a complete path will be difficult, because only one instance is passed.
Closest i know right now is by simply using expression.Body.ToString() which would result in b.ChildList.get_Item(0).Name as a result.
You would still have to remove the first b. from the string if not wanted, and you could go even further to your intended output with Regex by replacing the get_Item(0) with the typical Index-Accessor.
(Also i had to make the ChildList and the Name-Property of ChildModel public to get it to work)
This Should get you most of the way there:
public static string GetFullPath<T>(Expression<Func<T>> action)
{
var removeBodyPath = new Regex(#"value\((.*)\).");
var result = action.Body.ToString();
var replaced = removeBodyPath.Replace(result, String.Empty);
var seperatedFiltered = replaced.Split('.').Skip(1).ToArray();
return string.Join(".", seperatedFiltered);
}
It gets ugly quite quickly...
public static string GetExpressionText<T>(Expression<Func<T, object>> expression)
{
bool needDot = false;
Expression exp = expression.Body;
string descr = string.Empty;
while (exp != null)
{
if (exp.NodeType == ExpressionType.MemberAccess)
{
// Property or field
var ma = (MemberExpression)exp;
descr = ma.Member.Name + (needDot ? "." : string.Empty) + descr;
exp = ma.Expression;
needDot = true;
}
else if (exp.NodeType == ExpressionType.ArrayIndex)
{
// Array indexer
var be = (BinaryExpression)exp;
descr = GetParameters(new ReadOnlyCollection<Expression>(new[] { be.Right })) + (needDot ? "." : string.Empty) + descr;
exp = be.Left;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Index)
{
// Object indexer (not used by C#. See ExpressionType.Call)
var ie = (IndexExpression)exp;
descr = GetParameters(ie.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ie.Object;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Parameter)
{
break;
}
else if (exp.NodeType == ExpressionType.Call)
{
var ca = (MethodCallExpression)exp;
if (ca.Method.IsSpecialName)
{
// Object indexer
bool isIndexer = ca.Method.DeclaringType.GetDefaultMembers().OfType<PropertyInfo>().Where(x => x.GetGetMethod() == ca.Method).Any();
if (!isIndexer)
{
throw new Exception();
}
}
else if (ca.Object.Type.IsArray && ca.Method.Name == "Get")
{
// Multidimensiona array indexer
}
else
{
throw new Exception();
}
descr = GetParameters(ca.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ca.Object;
needDot = false;
}
}
return descr;
}
private static string GetParameters(ReadOnlyCollection<Expression> exps)
{
var values = new string[exps.Count];
for (int i = 0; i < exps.Count; i++)
{
if (exps[i].NodeType != ExpressionType.Constant)
{
throw new Exception();
}
var ce = (ConstantExpression)exps[i];
// Quite wrong here... We should escape string values (\n written as \n and so on)
values[i] = ce.Value == null ? "null" :
ce.Type == typeof(string) ? "\"" + ce.Value + "\"" :
ce.Type == typeof(char) ? "'" + ce.Value + "\'" :
ce.Value.ToString();
}
return "[" + string.Join(", ", values) + "]";
}
The code is quite easy to read, but it is quite long... There are 4 main cases: MemberAccess, that is accessing a property/field, ArrayIndex that is using the indexer of a single-dimensional array, Index that is unused by the C# compiler, but that should be using the indexer of an object (like the [...] of the List<> you are using), and Call that is used by C# for using an indexer or for accessing multi-dimensional arrays (new int[5, 4]) (and for other method calls, but we disregard them).
I support multidimensional arrays, jagged array s(arrays of arrays, new int[5][]) or arrays of indexable objects (new List<int>[5]) or indexable objects of indexable objects (new List<List<int>>). There is even support for multi-property indexers (indexers that use more than one key value, like obj[1, 2]). Small problem: printing the "value" of the indexers: I support only null, integers of various types, chars and strings (but I don't escape them... ugly... if there is a \n then it won't be printed as \n). Other types are not really supported... They will print what they will print (see GetParameters() if you want)
I have a basic rule engine that I've built in a very similar way to the route suggested here:
How to implement a rule engine?
Ive extended it based on further requirements, and now I need to evaluate complex classes eg
EvaluateRule("Transaction.IsOpen", "Equals", "true")
The code in its most basic form is:
var param = inputMessageType;
left = Expression.Property(param, memberName);
tProp = typeof(T).GetProperty(r.MemberName).PropertyType;
right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
return Expression.MakeBinary(tBinary, left, right);
In order to evaluate complex classes I used a method similar to here:
https://stackoverflow.com/questions/16544672/dynamically-evaluating-a-property-string-with-expressions
The problem that Im having is that when I try to evaluate the rule with a property of a class (Transaction.IsOpen), I get it with the type of the root type on the right hand side of the expression but the type of the complex object on the left hand side of the expression.
This results in the error:
System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Func`2[Transaction,System.Boolean]' and 'System.Boolean'.
How do I overcome this problem? I am no expert using Expression Trees, and many of the concepts are proving difficult to grasp when the example strays from the standard documentation.
Edit: Here is the code(Ive omitted some stuff that is environment specific so as keep focus with the problem)
public Actions EvaluateRulesFromMessage(ClientEventQueueMessage message)
{
var ruleGroups = _ruleRepository.GetRuleList();
var actions = new Actions();
foreach (var ruleGroup in ruleGroups)
{
if (message.MessageType == "UI_UPDATE")
{
// clean up json object
JObject dsPayload = (JObject.Parse(message.Payload));
var msgParams = JsonConvert.DeserializeObject<UiTransactionUpdate>(message.Payload);
msgParams.RulesCompleted = msgParams.RulesCompleted ?? new List<int>();
var conditionsMet = false;
// process the rules filtering out the rules that have already been evaluated
var filteredRules = ruleGroup.Rules.Where(item =>
!msgParams.RulesCompleted.Any(r => r.Equals(item.Id)));
foreach (var rule in filteredRules)
{
Func<UiTransactionUpdate, bool> compiledRule = CompileRule<UiTransactionUpdate>(rule, msgParams);
if (compiledRule(msgParams))
{
conditionsMet = true;
}
else
{
conditionsMet = false;
break;
}
}
if (conditionsMet)
{
actions = AddAction(message, ruleGroup);
break;
}
}
}
return actions;
}
public Func<UiTransactionUpdate, bool> CompileRule<T>(Rule r, UiTransactionUpdate msg)
{
var expression = Expression.Parameter(typeof(UiTransactionUpdate));
Expression expr = BuildExpr<UiTransactionUpdate>(r, expression, msg);
// build a lambda function UiTransactionUpdate->bool and compile it
return Expression.Lambda<Func<UiTransactionUpdate, bool>>(expr, expression).Compile();
}
static Expression Eval(object root, string propertyString, out Type tProp)
{
Type type = null;
var propertyNames = propertyString.Split('.');
ParameterExpression param = Expression.Parameter(root.GetType());
Expression property = param;
string propName = "";
foreach (var prop in propertyNames)
{
property = MemberExpression.PropertyOrField(property, prop);
type = property.Type;
propName = prop;
}
tProp = Type.GetType(type.UnderlyingSystemType.AssemblyQualifiedName);
var param2 = MemberExpression.Parameter(tProp);
var e = Expression.Lambda(property, param);
return e;
}
static Expression BuildExpr<T>(Rule r, ParameterExpression param, UiTransactionUpdate msg)
{
Expression left;
Type tProp;
string memberName = r.MemberName;
if (memberName.Contains("."))
{
left = Eval(msg, memberName, out tProp);
}
else
{
left = Expression.Property(param, memberName);
tProp = typeof(T).GetProperty(r.MemberName).PropertyType;
}
ExpressionType tBinary;
if (ExpressionType.TryParse(r.Operator, out tBinary))
{
Expression right=null;
switch (r.ValueType) ///todo: this needs to be refactored to be type independent
{
case TargetValueType.Value:
right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
break;
}
// use a binary operation ie true/false
return Expression.MakeBinary(tBinary, left, right);
}
else
{
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return Expression.Call(left, method, right);
}
}
The sample code does not cover all data types used in your scenario, so it's hard to tell where it breaks exactly, but from the exception System.Func'2[Transaction,System.Boolean]' and 'System.Boolean it's obvious that on left hand you have a delegate that takes in Transaction and returns bool (Func<Transaction, bool>), and on the right hand you just have bool.
It's not possible to compare Func<Transaction, bool> to bool, but it's possible to call the function and compare its result:
Func<Transaction, bool> func = ...;
bool comparand = ...;
Transaction transaction = ...;
if (func(transaction) == comparand) { ... }
What translated to expression tree:
Expression funcExpression = ... /*LambdaExpression<Func<Transaction,bool>>*/;
Expression comparandExpression = Expression.Constant(true);
Expression transactionArg = /*e.g.*/Expression.Constant(transaction);
Expression funcResultExpression = Expression.Call(funcExpression, "Invoke", null, transactionArg);
Expression equalityTestExpression = Expression.Equal(funcResultExpression, comparandExpression);