string expression to c# function delegate - c#

I want to convert the following string into function delegate.
[Id]-[Description]
C# class:
public class Foo
{
public string Id {get;set;}
public string Description {get;set;}
}
Result function delegate:
Func<Foo, string> GetExpression = delegate()
{
return x => string.Format("{0}-{1}", x.Id, x.Description);
};
I think compiled lambda or expression parser would be a way here, but not sure about the best way much. Any inputs?

It's possible as: to construct Linq Expression then compile it. Compiled expression is an ordinary delegate, with no performance drawbacks.
An example of implementation if type of argument(Foo) is known at compile time:
class ParserCompiler
{
private static (string format, IReadOnlyCollection<string> propertyNames) Parse(string text)
{
var regex = new Regex(#"(.*?)\[(.+?)\](.*)");
var formatTemplate = new StringBuilder();
var propertyNames = new List<string>();
var restOfText = text;
Match match;
while ((match = regex.Match(restOfText)).Success)
{
formatTemplate.Append(match.Groups[1].Value);
formatTemplate.Append("{");
formatTemplate.Append(propertyNames.Count);
formatTemplate.Append("}");
propertyNames.Add(match.Groups[2].Value);
restOfText = match.Groups[3].Value;
}
formatTemplate.Append(restOfText);
return (formatTemplate.ToString(), propertyNames);
}
public static Func<T, string> GetExpression<T>(string text) //"[Id]-[Description]"
{
var parsed = Parse(text); //"{0}-{1} Id, Description"
var argumentExpression = Expression.Parameter(typeof(T));
var properties = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetField)
.ToDictionary(keySelector: propInfo => propInfo.Name);
var formatParamsArrayExpr = Expression.NewArrayInit(
typeof(object),
parsed.propertyNames.Select(propName => Expression.Property(argumentExpression, properties[propName])));
var formatStaticMethod = typeof(string).GetMethod("Format", BindingFlags.Static | BindingFlags.Public, null,new[] { typeof(string), typeof(object[]) }, null);
var formatExpr = Expression.Call(
formatStaticMethod,
Expression.Constant(parsed.format, typeof(string)),
formatParamsArrayExpr);
var resultExpr = Expression.Lambda<Func<T, string>>(
formatExpr,
argumentExpression); // Expression<Func<Foo, string>> a = (Foo x) => string.Format("{0}-{1}", x.Id, x.Description);
return resultExpr.Compile();
}
}
And usage:
var func = ParserCompiler.GetExpression<Foo>("[Id]-[Description]");
var formattedString = func(new Foo {Id = "id1", Description = "desc1"});

An almost identical answer was posted while I was testing this, but, as the below code has an advantage of calling each property mentioned in the formatting string at most once, I'm posting it anyway:
public static Func<Foo, string> GetExpression(string query_string)
{
(string format_string, List<string> prop_names) = QueryStringToFormatString(query_string);
var lambda_parameter = Expression.Parameter(typeof(Foo));
Expression[] formatting_params = prop_names.Select(
p => Expression.MakeMemberAccess(lambda_parameter, typeof(Foo).GetProperty(p))
).ToArray();
var formatMethod = typeof(string).GetMethod("Format", new[] { typeof(string), typeof(object[]) });
var format_call = Expression.Call(formatMethod, Expression.Constant(format_string), Expression.NewArrayInit(typeof(object), formatting_params));
var lambda = Expression.Lambda(format_call, lambda_parameter) as Expression<Func<Foo, string>>;
return lambda.Compile();
}
// A *very* primitive parser, improve as needed
private static (string format_string, List<string> ordered_prop_names) QueryStringToFormatString(string query_string)
{
List<string> prop_names = new List<string>();
string format_string = Regex.Replace(query_string, #"\[.+?\]", m => {
string prop_name = m.Value.Substring(1, m.Value.Length - 2);
var known_pos = prop_names.IndexOf(prop_name);
if (known_pos < 0)
{
prop_names.Add(prop_name);
known_pos = prop_names.Count - 1;
}
return $"{{{known_pos}}}";
});
return (format_string, prop_names);
}
The inspiration comes from Generate lambda Expression By Clause using string.format in C#?.

A simple step by step version to create an Expression tree based on simple use case, can help in creating any kind of Expression tree
What we want to Achieve: (coding in linqpad, Dump is a print call)
Expression<Func<Foo,string>> expression = (f) => string.Format($"{f.Id}-
{f.Description}");
var foo = new Foo{Id = "1",Description="Test"};
var func = expression.Compile();
func(foo).Dump(); // Result "1-Test"
expression.Dump();
Following is the Expression generated:
Step by Step process to Create an Expression Tree
On Reviewing the Expression Tree, following points can be understood:
We create a Func delegate of type typeof(Func<Foo,String>)
Outer Node Type for Expression is Lambda Type
Just needs one parameter Expression of typeof(Foo)
In Arguments it needs, MethodInfo of string.Format
In arguments to Format method, it needs following Expressions
a.) Constant Expression - {0}-{1}
b.) MemberExpression for Id field
c.) MemberExpression for Description field
Viola and we are done
Using the Steps above following is the simple code to create Expression:
// Create a ParameterExpression
var parameterExpression = Expression.Parameter(typeof(Foo),"f");
// Create a Constant Expression
var formatConstant = Expression.Constant("{0}-{1}");
// Id MemberExpression
var idMemberAccess = Expression.MakeMemberAccess(parameterExpression, typeof(Foo).GetProperty("Id"));
// Description MemberExpression
var descriptionMemberAccess = Expression.MakeMemberAccess(parameterExpression, typeof(Foo).GetProperty("Description"));
// String.Format (MethodCallExpression)
var formatMethod = Expression.Call(typeof(string),"Format",null,formatConstant,idMemberAccess,descriptionMemberAccess);
// Create Lambda Expression
var lambda = Expression.Lambda<Func<Foo,string>>(formatMethod,parameterExpression);
// Create Func delegate via Compilation
var func = lambda.Compile();
// Execute Delegate
func(foo).Dump(); // Result "1-Test"

Related

How to create an array Expression?

I have a mongoDB collection which I would like to filter by creating a expression based upon parameters.
string desiredExpress = #"x['columnName'].ToString() == value";
The system.linq.expressions namespace does have an arrayAccess expression but this is limited to type int32 which prevents the reference by column name.
Update including code snippet. The following code produces the expression x => x == x:
private static Expression<Func<BsonDocument, bool>> BuildPredicate(JArray valueNames, string row, string[] colNames)
{
Expression predicate = null;
ParameterExpression argParam = Expression.Parameter(typeof(BsonDocument), "s");
List<Tuple<string, string>> parameters = valueNames
.Select(vn => Tuple.Create(vn[TARGET].ToString(), row.Split(',')[GetFieldIndex(new Field { name = vn[TARGET].ToString() }, colNames)])).ToList();
foreach(var parameter in parameters)
{
Expression e = Expression.Equal(Expression.Constant(parameter.Item2), Expression.Constant(parameter.Item2));
predicate = predicate == null ? e : Expression.AndAlso(predicate, e);
}
return Expression.Lambda<Func<BsonDocument, bool>>(predicate, argParam);
}

Integer contains linq c# but using using expressions

I want to create a dynamic filter for my repositories using linq expressions, I have other filters but i don't know how to make the next one using expressions: (the condition was taked from here)
var result = _studentRepotory.GetAll().Where(s =>
SqlFunctions.StringConvert((double)s.Id).Contains("91")).ToList();
I have a method that receives the value of a property, the propertyName and the filter operator type (enum):
public static class Helper {
public static Expression<Func<T, bool>> GetExpression<T>(object value, string propertyName, FilterOperatorType FilterType)
{
var parameter = Expression.Parameter(typeof(T));
var property = ExpressionUtils.GetPropertyChild(parameter, propertyName);
var constValue = Expression.Constant(value);
BinaryExpression expresion = null;
switch (FilterType)
{
case FilterOperatorType.Equal:
expresion = Expression.Equal(property, constValue);
break;
case FilterOperatorType.Greater:
expresion = Expression.GreaterThan(property, constValue);
break;
case FilterOperatorType.Less:
expresion = Expression.LessThan(property, constValue);
break;
case FilterOperatorType.GreaterOrEqual:
expresion = Expression.GreaterThanOrEqual(property, constValue);
break;
case FilterOperatorType.LessOrEqual:
expresion = Expression.LessThanOrEqual(property, constValue);
break;
}
var lambda = Expression.Lambda<Func<T, bool>>(expresion, parameter);
return lambda;
}
}
So, I want to add a new opertator type Contains that will evaluate if an integer contains some digits, in the first block of code I do it, but I want to do it with linq expressions using generics.
At the end I wil have:
Expression<Func<Student, bool>> where = Helper.GetExpression<Student>("91", "Id", FilterOperatorType.Contains);
var result = _studentRepotory.GetAll().Where(where).ToList();
The query should return all the students when the Id contains the digits 91.
Please help me, and tell me if you understand.
I'm at 2:00 am and still working on this, the brain works better at that time hehehe, here is the solution for create the expression:
object value = "91";
var parameter = Expression.Parameter(typeof(Student));
var property = ExpressionUtils.GetPropertyChild(parameter, "Id");
var constValue = Expression.Constant(value);
var expressionConvert = Expression.Convert(property, typeof(double?));
var methodStringConvert = typeof(SqlFunctions).GetMethod("StringConvert",
BindingFlags.Public | BindingFlags.Static, null,
CallingConventions.Any,
new Type[] { typeof(double?) },
null);
var methodContains = typeof(string).GetMethod("Contains",
BindingFlags.Public | BindingFlags.Instance,
null, CallingConventions.Any,
new Type[] { typeof(String) }, null);
var expresionStringConvert = Expression.Call(methodStringConvert, expressionConvert);
var expresionContains = Expression.Call(expresionStringConvert, methodContains, constValue);
var lambdaContains = Expression.Lambda<Func<Student, bool>>(expresionContains, parameter);
Now you can use lambdaContains in the Where method of the studentRepository
Here is a method. And you can search int columns as strings
https://gist.github.com/gnncl/e424adefc3e08b607beee8d638759492

Convert params args to a lambda expression in Entity Framework

I am looking to take the params string[] args and convert it to a lambda expression for Entity Framework.
Something like this...
public main(params string[] args)
{
DataContext context = new DataContext();
foreach(string arg in args)
{
//build Query
}
context.Things.Where(/*Query*/);
}
You can use dynamic linq to create string based expressions.
https://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library
The downside is that these expression are evaluated at runtime, so you won't be able to catch errors at compile time.
Or you can use Expression trees to construct expressions .
https://msdn.microsoft.com/en-us/library/bb882637(v=vs.110).aspx
This will be a bit more code, but you will get the advantage of compile time type checks.
An excellent article is here https://www.codeproject.com/Articles/1079028/Build-Lambda-Expressions-Dynamically
Here is my solution:
public static Expression<Func<TClass, bool>> ConvertParamArgsToExpression<TClass>(string[] args)
{
Expression finalExpression = Expression.Constant(true);
var parameter = Expression.Parameter(typeof(TClass), "x");
foreach (string arg in args) {
string[] values = arg.Split('=');
PropertyInfo prop = typeof(TClass).GetProperty(values[0]);
if(prop != null)
{
Expression expression = null;
var member = Expression.Property(parameter, prop.Name);
var constant = Expression.Constant(values[1]);
expression = Expression.Equal(member, constant);
finalExpression = Expression.AndAlso(finalExpression, expression);
}
}
return (Expression.Lambda<Func<TClass, bool>>(finalExpression, parameter));
}
Usage:
Expression<Func<AdminPageObject, bool>> expression = LambdaConverter.ConvertParamArgsToExpression<AdminPageObject>(args);
if(expression != null)
{
items = items.Where(expression);
}

Lambda Expression for dynamic Object

I am trying to build a Lambda Expression for a table that has been created at run time.
The Expression is build fine but when I call Compile() method I get this error
"ParameterExpression of type 'cseval.Item' cannot be used for delegate parameter of type 'System.Object'"
this is my function
public Func<dynamic, Boolean> GetWhereExp(List<WhereCondition> SearchFieldList, dynamic item)
{
ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (WhereCondition fieldItem in SearchFieldList)
{
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
//the name constant to match
Expression columnValue = Expression.Constant(fieldItem.Value);
//the first expression: PatientantLastName = ?
Expression e1 = Expression.Equal(columnNameProperty, columnValue);
if (combined == null)
{
combined = e;
}
else
{
combined = Expression.And(combined, e);
}
}
}
var result = Expression.Lambda<Func<dynamic, bool>>(combined, pe);
return result.Compile();
}
I've changed dynamic to generics, this code works for me:
public Func<T, Boolean> GetWhereExp<T>(List<WhereCondition> SearchFieldList, T item)
{
var pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (var fieldItem in SearchFieldList)
{
var columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
var columnValue = Expression.Constant(fieldItem.Value);
var e1 = Expression.Equal(columnNameProperty, columnValue);
combined = combined == null ? e1 : Expression.And(combined, e1);
}
}
var result = Expression.Lambda<Func<T, bool>>(combined, pe);
return result.Compile();
}
Small remark: your method returns function, not an expression, so the name 'GetWhereExp' is slightly incorrect. If you want to return function, imho, it's better to use reflection.
UPD: I use this code to test:
var expressions = new List<WhereCondition>
{
new WhereCondition("Column1", "xxx"),
new WhereCondition("Column2", "yyy"),
};
var item = new
{
Column1 = "xxx",
Column2 = "yyy"
};
var func = LinqExpr.GetWhereExp(expressions, (dynamic)item);
Console.WriteLine(new[] {item}.Count(a => func(a)));

Using expression trees to construct an object with unknown members

I'm trying to create a generic function that will take 2 instances of a struct and create a new instance using the values of the passed-in instances. I'm mostly there but I'm having trouble figuring out how to build the expression tree to take the new values as parameters in the MemberInit (first time using expression trees).
I'm trying to avoid creating garbage (so no boxing) as much as possible.
Here's what I have so far:
private static readonly Dictionary<Type, FieldInfo[]> fieldInfoCache = new Dictionary<Type, FieldInfo[]>();
private static readonly Dictionary<FieldInfo, dynamic> compiledDelegates = new Dictionary<FieldInfo, dynamic>();
private static T Lerp<T>(T start, T end, float amount) where T : new()
{
FieldInfo[] fields;
var type = typeof(T);
if(!fieldInfoCache.TryGetValue(type, out fields))
{
fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
fieldInfoCache.Add(type, fields);
}
var binds = new List<MemberBinding>();
foreach(var fieldInfo in fields)
{
dynamic getter;
if(!compiledDelegates.TryGetValue(fieldInfo, out getter))
{
var targetExp = Expression.Parameter(type, type.Name);
var fieldExp = Expression.Field(targetExp, fieldInfo);
getter = Expression.Lambda(typeof(Func<,>).MakeGenericType(type, fieldInfo.FieldType), fieldExp, targetExp).Compile();
compiledDelegates.Add(fieldInfo, getter);
}
var startVal = getter.Invoke(start);
var endVal = getter.Invoke(end);
//This needs to be assigned to something
var newVal = fieldInfo.FieldType.IsAssignableFrom(typeof(float)) ? LerpF(startVal, endVal, amount) : Lerp(startVal, endVal, amount);
var fieldParamExp = Expression.Parameter(fieldInfo.FieldType, "newVal");
var bind = Expression.Bind(fieldInfo, fieldParamExp);
binds.Add(bind);
}
//How do I fix these two lines?
var memberInit = Expression.MemberInit(Expression.New(type), binds);
var result = Expression.Lambda<Func<T>>(memberInit).Compile().Invoke();
return result;
}
The part that I'm stumped on is how to feed the values into those last 2 lines without causing boxing
Instead of
var fieldParamExp = Expression.Parameter(fieldInfo.FieldType, "newVal");
try use
var fieldParamExp = Expression.Constant(newVal);
Update:
For efficient cache you could use something like
var startPar = Expression.Parameter(typeof (T), "start");
var endPar = Expression.Parameter(typeof (T), "end");
var amountPar = Expression.Parameter(typeof (float), "amount");
foreach (var fieldInfo in fields)
{
MethodInfo mi;
if (fieldInfo.FieldType.IsAssignableFrom(typeof (float)))
{
mi = typeof (Program).GetMethod("LerpF");
}
else
{
mi = typeof (Program).GetMethod("Lerp").MakeGenericMethod(fieldInfo.FieldType);
}
var makeMemberAccess = Expression.Call(mi, Expression.MakeMemberAccess(startPar, fieldInfo), Expression.MakeMemberAccess(endPar, fieldInfo), amountPar);
binds.Add(Expression.Bind(fieldInfo, makeMemberAccess));
}
var memberInit = Expression.MemberInit(Expression.New(type), binds);
var expression = Expression.Lambda<Func<T, T, float, T>>(memberInit, startPar, endPar, amountPar);
Func<T, T, float, T> resultFunc = expression.Compile();
// can cache resultFunc
var result = resultFunc(start, end, amount);
But I don't know how you decide to use start or end parameter, so there may be some more complex conditions in bindings.

Categories