is it possible to create an anonymous method in c# from a string?
e.g. if I have a string "x + y * z" is it possible to turn this into some sort of method/lambda object that I can call with arbitrary x,y,z parameters?
It's possible, yes. You have to parse the string and, for example, compile a delegate using expression trees.
Here's an example of creating (x, y, z) => x + y * z using expression trees:
ParameterExpression parameterX = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterY = Expression.Parameter(typeof(int), "y");
ParameterExpression parameterZ = Expression.Parameter(typeof(int), "z");
Expression multiplyYZ = Expression.Multiply(parameterY, parameterZ);
Expression addXMultiplyYZ = Expression.Add(parameterX, multiplyYZ);
Func<int,int,int,int> f = Expression.Lambda<Func<int, int, int, int>>
(
addXMultiplyYZ,
parameterX,
parameterY,
parameterZ
).Compile();
Console.WriteLine(f(24, 6, 3)); // prints 42 to the console
Just for fun using CodeDom (any valid C# code is allowed in the string as long as it is present in mscorlib (No check for errors at all):
static class Program
{
static string code = #"
public static class __CompiledExpr__
{{
public static {0} Run({1})
{{
return {2};
}}
}}
";
static MethodInfo ToMethod(string expr, Type[] argTypes, string[] argNames, Type resultType)
{
StringBuilder argString = new StringBuilder();
for (int i = 0; i < argTypes.Length; i++)
{
if (i != 0) argString.Append(", ");
argString.AppendFormat("{0} {1}", argTypes[i].FullName, argNames[i]);
}
string finalCode = string.Format(code, resultType != null ? resultType.FullName : "void",
argString, expr);
var parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("mscorlib.dll");
parameters.ReferencedAssemblies.Add(Path.GetFileName(Assembly.GetExecutingAssembly().Location));
parameters.GenerateInMemory = true;
var c = new CSharpCodeProvider();
CompilerResults results = c.CompileAssemblyFromSource(parameters, finalCode);
var asm = results.CompiledAssembly;
var compiledType = asm.GetType("__CompiledExpr__");
return compiledType.GetMethod("Run");
}
static Action ToAction(this string expr)
{
var method = ToMethod(expr, new Type[0], new string[0], null);
return () => method.Invoke(null, new object[0]);
}
static Func<TResult> ToFunc<TResult>(this string expr)
{
var method = ToMethod(expr, new Type[0], new string[0], typeof(TResult));
return () => (TResult)method.Invoke(null, new object[0]);
}
static Func<T, TResult> ToFunc<T, TResult>(this string expr, string arg1Name)
{
var method = ToMethod(expr, new Type[] { typeof(T) }, new string[] { arg1Name }, typeof(TResult));
return (T arg1) => (TResult)method.Invoke(null, new object[] { arg1 });
}
static Func<T1, T2, TResult> ToFunc<T1, T2, TResult>(this string expr, string arg1Name, string arg2Name)
{
var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2) },
new string[] { arg1Name, arg2Name }, typeof(TResult));
return (T1 arg1, T2 arg2) => (TResult)method.Invoke(null, new object[] { arg1, arg2 });
}
static Func<T1, T2, T3, TResult> ToFunc<T1, T2, T3, TResult>(this string expr, string arg1Name, string arg2Name, string arg3Name)
{
var method = ToMethod(expr, new Type[] { typeof(T1), typeof(T2), typeof(T3) },
new string[] { arg1Name, arg2Name, arg3Name }, typeof(TResult));
return (T1 arg1, T2 arg2, T3 arg3) => (TResult)method.Invoke(null, new object[] { arg1, arg2, arg3 });
}
static void Main(string[] args)
{
var f = "x + y * z".ToFunc<int, int, long, long>("x", "y", "z");
var x = f(3, 6, 8);
}
}
C# doesn't have any functionality like this (other languages - like JavaScript - have eval functions to handle stuff like this). You will need to parse the string and create a method yourself with either expression trees or by emitting IL.
There are functionality to do this in the .Net framework.
It is not easy. You need to add some code around the statement to make it into a complete assembly including a class and method you can call.
After that you pass the string to
CSharpCodeProvider.CompileAssemblyFromSource(options, yourcode);
Here is an example
It could be possible with a grammar (e.g. ANTLR) and an interpreter which creates expression trees. This is no small task, however, you can be successful if you limit the scope of what you accept as input. Here are some references:
C# ANTLR3 Grammar (complex, but you could extract a portion)
Expression Trees
Here is what some code may look like to transform an ANTLR ITree into an Expression tree. It isn't complete, but shows you what you're up against.
private Dictionary<string, ParameterExpression> variables
= new Dictionary<string, ParameterExpression>();
public Expression Visit(ITree tree)
{
switch(tree.Type)
{
case MyParser.NUMBER_LITERAL:
{
float value;
var literal = tree.GetChild(0).Text;
if (!Single.TryParse(literal, out value))
throw new MyParserException("Invalid number literal");
return Expression.Constant(value);
}
case MyParser.IDENTIFIER:
{
var ident = tree.GetChild(0).Text;
if (!this.variables.ContainsKey(ident))
{
this.variables.Add(ident,
Expression.Parameter(typeof(float), ident));
}
return this.variables[ident];
}
case MyParser.ADD_EXPR:
return Expression.Add(Visit(tree.GetChild(0)), Visit(tree.GetChild(1)));
// ... more here
}
}
Related
I hope somebody can guide and help me with this. We have an inherited project that uses ExpressionHelper class. Basically, this Expression Helper will return an IQueryable that build a dynamic query base on the search term that the user provided.
For example, I have the below code where I pass 2 search terms.
IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();;
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "John" }
};
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var obj = ExpressionHelper.Parameter<TEntity>();
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider
.GetComparison(left, searchTerm.Operator, right);
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper
.GetLambda<TEntity, bool>(obj, comparisonExpression);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
With the code above and using the below ExpressionHelper class, this generate the below SQL query when I check using SQLProfiler. Please notice the AND in the query. What I actually what is OR.
Constructed QUERY in SQL Profiler
SELECT
[Extent1].[FirstName] AS [FirstName],
FROM [dbo].[tblUser] AS [Extent1]
WHERE ([Extent1].[Conatact1] = N'Bob') AND ([Extent1].[Contact2] = N'John')
ExpressionHelper.cs
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo[] QueryableMethods = typeof(Queryable)
.GetMethods()
.ToArray();
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
{
var whereMethodBuilder = QueryableMethods
.First(x => x.Name == "Where" && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(T) });
return (IQueryable<T>)whereMethodBuilder
.Invoke(null, new object[] { query, predicate });
}
public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
IQueryable<TEntity> modifiedQuery,
bool useThenBy,
bool descending,
Type propertyType,
LambdaExpression keySelector)
{
var methodName = "OrderBy";
if (useThenBy) methodName = "ThenBy";
if (descending) methodName += "Descending";
var method = QueryableMethods
.First(x => x.Name == methodName && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(TEntity), propertyType });
return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
}
}
I have hard time understanding on how the query was created and how do I change it to become OR in the created query.
Hope someone can guide me and point to the right direction. Thank you!
Add to SearchTerm a new property (C# 6.0 syntax here):
// This is quite wrong. We should have an enum here, but Operator is
// done as a string, so I'm maintaining the "style"
// Supported LogicalConnector: and, or
public string LogicalConnector { get; set; } = "and";
}
Then:
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
Expression comparisonExpressions = null;
var obj = ExpressionHelper.Parameter<TEntity>();
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);
if (comparisonExpressions == null)
{
comparisonExpressions = comparisonExpression;
}
else if (searchTerm.LogicalConnector == "and")
{
comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
}
else if (searchTerm.LogicalConnector == "or")
{
comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
}
else
{
throw new NotSupportedException(searchTerm.LogicalConnector);
}
}
if (comparisonExpressions != null)
{
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
return modifiedQuery;
}
Use it like:
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};
IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
Note that there is no way in this code to explicitly set brackets, that will be implicitly set as:
(((A opB b) opC C) opD D)
Where A, B, C, D are the SearchTerm[0], SearchTerm[1], SearchTerm[2], SearchTerm[3] and opB, opC, opD are the operators defined in SearchTerm[1].LogicalConnector, SearchTerm[2].LogicalConnector, SearchTerm[3].LogicalConnector.
While putting brackets is easy, choosing how to "describe" them is complex, unless you change significantly your SearchTerm collection (it couldn't be a "linear" array but it would need to be a tree).
P.S. I was wrong, you don't need an ExpressionVisitor. You need an ExpressionVisitor when you are trying to "merge" multiple LambdaExpressions that have distinct ParameterExpression. In this code we are able to have a single var obj = ExpressionHelper.Parameter<TEntity>() for all the query, so no problems merging the conditions. To make it clear: if you want to "merge" x1 => x1.Foo == "Foo1" with x2 => x2.Foo == "Foo2" then you need an ExpressionVisitor that replaces x2 with x1, otherwise you would get a wrong query like x1 => x1.Foo == "Foo1" || x2.Foo == "Foo2". In the code given we have only x1 (that is var obj = ExpressionHelper.Parameter<TEntity>()), so no problem.
I have an object of any of the func types func<>, Func<,>, func<,,> ... And I'd like to replace one of the input parameters with a constant value.
eg:
object SetParameter<T>(object function, int index, T value){
//I don't know how to code this.
}
Func<int, String, String> function = (a, b) => a.ToString() + b;
object objectFunction = function;
object newFunction = SetParameter<int>(objectFunction, 0, 5);
// Here the new function should be a Func<String, String> which value "(b) => function(5, b)"
I already now how to get the type of the resulting function, but that does not really help me in implementing the desired behavior:
private Type GetNewFunctionType<T>(object originalFunction, int index, T value)
{
Type genericType = originalFunction.GetType();
if (genericType.IsGenericType)
{
var types = genericType.GetGenericArguments().ToList();
types.RemoveAt(index);
Type genericTypeDefinition = genericType.GetGenericTypeDefinition();
return genericTypeDefinition.MakeGenericType(types.ToArray());
}
throw new InvalidOperationException($"{nameof(originalFunction)} must be a generic type");
}
It's not quite clear what the purpose of your conversion is for, but wouldn't it be easier to avoid all the reflection. E.g.:
Func<int, string, string> func3 = (a, b) => a.ToString() + b;
Func<string, string> func3withConst = (b) => func3(10, b);
Since you are talking about a very limited scope (supporting just Func<TReturn>, Func<T1, TReturn> and Func<T1, T2, TReturn>) doing this through reflection is much more error prone and harder to read.
Just in case you need to use expression tree to build the function:
object SetParameter<T>(object function, int index, T value)
{
var parameterTypes = function.GetType().GetGenericArguments();
// Skip where i == index
var newFuncParameterTypes = parameterTypes.SkipWhile((_, i) => i == index).ToArray();
// Let's assume function is Fun<,,> to make this example simple :)
var newFuncType = typeof(Func<,>).MakeGenericType(newFuncParameterTypes);
// Now build a new function using expression tree.
var methodCallParameterTypes = parameterTypes.Reverse().Skip(1).Reverse().ToArray();
var methodCallParameters = methodCallParameterTypes.Select(
(t, i) => i == index
? (Expression)Expression.Constant(value, typeof(T))
: Expression.Parameter(t, "b")
).ToArray();
// func.Invoke(5, b)
var callFunction = Expression.Invoke(
Expression.Constant(function),
methodCallParameters);
// b => func.Invoke(5, b)
var newFunc = Expression.Lambda(
newFuncType,
callFunction,
methodCallParameters.OfType<ParameterExpression>()
).Compile();
return newFunc;
}
To use this:
Func<int, string, string> func = (a, b) => a.ToString() + b;
var newFunc = (Func<string, string>)SetParameter<int>(func, 0, 5);
// Output: 5b
Console.WriteLine(newFunc("b"));
I have a list comparison function which is given below
public delegate bool CompareValue<in T1, in T2>(T1 val1, T2 val2);
public static bool CompareTwoLists<T1, T2>(IEnumerable<T1> list1, IEnumerable<T2> list2, CompareValue<T1, T2> compareValue)
{
return list1.Select(item1 => list2.Any(item2 => compareValue(item1, item2))).All(search => search)
&& list2.Select(item2 => list1.Any(item1 => compareValue(item1, item2))).All(search => search);
}
And this function is calling like below
bool IsSuccess1 = ListComparison.CompareTwoLists(listProduct, listProduct2, (listProductx, listProductx2) => listProductx.ProductName == listProductx2.ProductName);
Here delegate expression is static now. This is using to compare a particular column in two list items. How to create above code dynamically based on a list
List<CompareColumns> listCompareColumns = new List<Classes.CompareColumns>();
CompareColumns objDup1 = new CompareColumns();
objDup1.Columns = "ProductName";
CompareColumns objDup2 = new CompareColumns();
objDup2.Columns = "ProductCode";
listCompareColumns.Add(objDup1);
listCompareColumns.Add(objDup2);
Above list may have one or more columns. We need to compare these columns in the comparison lists. based on the above list how to generate below code dynamically?
(listProductx, listProductx2) => listProductx.ProductName == listProductx2.ProductName && listProductx.ProductCode == listProductx2.ProductCode && etc....
If you are going to use the comparison function (CompareValue<T1, T2> compareValue) a lot of times, then it would make sense to create an expression and compile it. Here is an example:
public static CompareValue<T1, T2> CreateComparer<T1, T2>(List<CompareColumns> columns)
{
var propertyNames = columns.Select(x => x.Columns).ToList();
var param1 = Expression.Parameter(typeof (T1), "val1");
var param2 = Expression.Parameter(typeof (T2), "val2");
var expressionBody =
propertyNames
.Select(propertyName =>
Expression.Equal(
Expression.Property(param1, propertyName),
Expression.Property(param2, propertyName)))
.Aggregate(Expression.AndAlso);
return
Expression
.Lambda<CompareValue<T1, T2>>(
expressionBody,
param1,
param2)
.Compile();
}
Here is how to use it:
List<CompareColumns> columns = ...
var func = CreateComparer<Class1,Class2>(columns);
var result = CompareTwoLists<Class1,Class2>(listOfClass1, listOfClass2 , func);
Assuming listCompareColumns can be defined in compareValue function you can do this using Reflection:
bool CmpVal<T1, T2>(T1 val1, T2 val2)
{
var listCompareColumns = new List<CompareColumns>();
//fill list values
return listCompareColumns
.All(q => typeof(T1).GetProperty(q.Columns, BindingFlags.Public |
BindingFlags.Instance)
.GetValue(val1) ==
typeof(T2).GetProperty(q.Columns, BindingFlags.Public |
BindingFlags.Instance)
.GetValue(val2));
}
I have small piece of code responsible for dynamic extraction of properties values from objects instances through reflection:
public static object ExtractValue(object source, string property)
{
var props = property.Split('.');
var type = source.GetType();
var arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (var prop in props)
{
var pi = type.GetProperty(prop);
if (pi == null)
throw new ArgumentException(string.Format("Field {0} not found.", prop));
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
var delegateType = typeof(Func<,>).MakeGenericType(source.GetType(), type);
var lambda = Expression.Lambda(delegateType, expr, arg);
var compiledLambda = lambda.Compile();
var value = compiledLambda.DynamicInvoke(source);
return value;
}
It can extract values of nested properties, like: ExtractValue(instance, "PropA.PropB.PropC").
Despite the fact I like this method and its implementation, when, say, PropB is null, DynamicInvoke() just throws NullReferenceException (wrapped by TargetInvocationException). Because I needed to know which exact property is null is such case, I modified its body a bit (standard step-by-step extraction chain):
public static object ExtractValue(object source, string property)
{
var props = property.Split('.');
for (var i = 0; i < props.Length; i++)
{
var type = source.GetType();
var prop = props[i];
var pi = type.GetProperty(prop);
if (pi == null)
throw new ArgumentException(string.Format("Field {0} not found.", prop));
source = pi.GetValue(source, null);
if (source == null && i < props.Length - 1)
throw new ArgumentNullException(pi.Name, "Extraction interrupted.");
}
return source;
}
Now it looks a bit worse (I like lambdas) but behaves much better, not only because it gives more meaningful information of what has failed, but also because this version is about 66 times faster than the first one (coarse test below):
var model = new ModelA
{
PropB = new ModelB {PropC = new ModelC {PropD = new ModelD {PropE = new ModelE {PropF = "hey"}}}}
};
const int times = 1000000;
var start = DateTime.Now;
for (var i = 0; i < times; i++)
ExtractValueFirst(model, "PropB.PropC.PropD.PropE.PropF");
var ticks_first = (DateTime.Now - start).Ticks;
Console.WriteLine(":: first - {0} iters tooks {1} ticks", times, ticks_first);
start = DateTime.Now;
for (var i = 0; i < times; i++)
ExtractValueSecond(model, "PropB.PropC.PropD.PropE.PropF");
var ticks_second= (DateTime.Now - start).Ticks;
Console.WriteLine(":: second - {0} iters tooks {1} ticks", times, ticks_second);
Console.WriteLine("ticks_first/ticks_second: {0}", (float)ticks_first / ticks_second);
Console.ReadLine();
How can this code be optimized in .NET to perform even faster (caching, direct IL maybe, etc)?
You can increase performance significantly by caching the compiled delegates:
static readonly ConcurrentDictionary<Tuple<Type,string>,Delegate> _delegateCache = new ConcurrentDictionary<Tuple<Type,string>,Delegate>();
public static object ExtractValue(object source, string expression)
{
Type type = source.GetType();
Delegate del = _delegateCache.GetOrAdd(new Tuple<Type,string>(type,expression),key => _getCompiledDelegate(key.Item1,key.Item2));
return del.DynamicInvoke(source);
}
// if you want to acces static aswell...
public static object ExtractStaticValue(Type type, string expression)
{
Delegate del = _delegateCache.GetOrAdd(new Tuple<Type,string>(type,expression),key => _getCompiledDelegate(key.Item1,key.Item2));
return del.DynamicInvoke(null);
}
private static Delegate _getCompiledDelegate(Type type, string expression)
{
var arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (var prop in property.Split('.'))
{
var pi = type.GetProperty(prop);
if (pi == null) throw new ArgumentException(string.Format("Field {0} not found.", prop));
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
var delegateType = typeof(Func<,>).MakeGenericType(source.GetType(), type);
var lambda = Expression.Lambda(delegateType, expr, arg);
return lambda.Compile();
}
I've done some execution time measurements, which are presented below:
private static Func<object, object> _cachedFunc;
private static Delegate _cachedDel;
static void Main(string[] args)
{
var model = new ModelA
{
PropB = new ModelB {PropC = new ModelC {PropD = new ModelD {PropE = new ModelE {PropF = "hey"}}}}
};
const string property = "PropB.PropC.PropD.PropE.PropF";
var watch = new Stopwatch();
var t1 = MeasureTime(watch, () => ExtractValueDelegate(model, property), "compiled delegate dynamic invoke");
var t2 = MeasureTime(watch, () => ExtractValueCachedDelegate(model, property), "compiled delegate dynamic invoke / cached");
var t3 = MeasureTime(watch, () => ExtractValueFunc(model, property), "compiled func invoke");
var t4 = MeasureTime(watch, () => ExtractValueCachedFunc(model, property), "compiled func invoke / cached");
var t5 = MeasureTime(watch, () => ExtractValueStepByStep(model, property), "step-by-step reflection");
var t6 = MeasureTime(watch, () => ExtractValueStandard(model), "standard access (model.prop.prop...)");
Console.ReadLine();
}
public static long MeasureTime<T>(Stopwatch sw, Func<T> funcToMeasure, string funcName)
{
const int times = 100000;
sw.Reset();
sw.Start();
for (var i = 0; i < times; i++)
funcToMeasure();
sw.Stop();
Console.WriteLine(":: {0, -45} - {1} iters tooks {2, 10} ticks", funcName, times, sw.ElapsedTicks);
return sw.ElapsedTicks;
}
public static object ExtractValueDelegate(object source, string property)
{
var ptr = GetCompiledDelegate(source.GetType(), property);
return ptr.DynamicInvoke(source);
}
public static object ExtractValueCachedDelegate(object source, string property)
{
var ptr = _cachedDel ?? (_cachedDel = GetCompiledDelegate(source.GetType(), property));
return ptr.DynamicInvoke(source);
}
public static object ExtractValueFunc(object source, string property)
{
var ptr = GetCompiledFunc(source.GetType(), property);
return ptr(source); //return ptr.Invoke(source);
}
public static object ExtractValueCachedFunc(object source, string property)
{
var ptr = _cachedFunc ?? (_cachedFunc = GetCompiledFunc(source.GetType(), property));
return ptr(source); //return ptr.Invoke(source);
}
public static object ExtractValueStepByStep(object source, string property)
{
var props = property.Split('.');
for (var i = 0; i < props.Length; i++)
{
var type = source.GetType();
var prop = props[i];
var pi = type.GetProperty(prop);
if (pi == null)
throw new ArgumentException(string.Format("Field {0} not found.", prop));
source = pi.GetValue(source, null);
if (source == null && i < props.Length - 1)
throw new ArgumentNullException(pi.Name, "Extraction interrupted.");
}
return source;
}
public static object ExtractValueStandard(ModelA source)
{
return source.PropB.PropC.PropD.PropE.PropF;
}
private static Func<object, object> GetCompiledFunc(Type type, string property)
{
var arg = Expression.Parameter(typeof(object), "x");
Expression expr = Expression.Convert(arg, type);
var propType = type;
foreach (var prop in property.Split('.'))
{
var pi = propType.GetProperty(prop);
if (pi == null) throw new ArgumentException(string.Format("Field {0} not found.", prop));
expr = Expression.Property(expr, pi);
propType = pi.PropertyType;
}
expr = Expression.Convert(expr, typeof(object));
var lambda = Expression.Lambda<Func<object, object>>(expr, arg);
return lambda.Compile();
}
private static Delegate GetCompiledDelegate(Type type, string property)
{
var arg = Expression.Parameter(type, "x");
Expression expr = arg;
var propType = type;
foreach (var prop in property.Split('.'))
{
var pi = propType.GetProperty(prop);
if (pi == null) throw new ArgumentException(string.Format("Field {0} not found.", prop));
expr = Expression.Property(expr, pi);
propType = pi.PropertyType;
}
var delegateType = typeof(Func<,>).MakeGenericType(type, propType);
var lambda = Expression.Lambda(delegateType, expr, arg);
return lambda.Compile();
}
Btw: As you can see I've omitted storing compiled lambdas inside dictionary (like in the answer qiven by CSharpie), because dictionary lookup is time consuming when you compare it to compiled lambdas execution time.
I am using the code below to execute methods. It works for standard methods of string , for example StartsWith, however I am attempting to use several string extension methods, including an override for Contains which alllows for case-insensitivity:
var method = "Contains";
var args = new object[] { "#", StringComparison.OrdinalIgnoreCase };
// This works when called
var mi = m.Type.GetMethod(method, args.Select(a => a.GetType()).ToArray());
if (mi == null) {
// This does find the method, but causes an error on Expression.Call below
mi = typeof(Extensions).GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(meth => meth.Name == method);
}
var c = args.Select(a => Expression.Constant(a, a.GetType()));
return Expression.Lambda<Func<T, bool>>(Expression.Call(m, mi, c), e);
The error I receive is:
Static method requires null instance, non-static method requires non-null instance.
How can I solve this?
For reference, here is the Contains extension:
public static bool Contains(this string source, string toCheck, StringComparison comp) {
return source.IndexOf(toCheck, comp) >= 0;
}
Thanks
EDIT
Updated as per Balazs answer, although I now get the following error:
Expression of type 'System.Linq.Expressions.PropertyExpression' cannot be used for parameter of type 'System.String' of method 'Boolean Contains(System.String, System.String, System.StringComparison)'
Here is the full method for reference:
public static Expression<Func<T, bool>> Build(string member, IEnumerable<object> memberArgs, string method, params object[] args) {
var e = Expression.Parameter(_type, "e");
var memberInfo =
(MemberInfo) _type.GetField(member) ??
(MemberInfo) _type.GetProperty(member) ??
(MemberInfo) _type.GetMethod(member, (memberArgs ?? Enumerable.Empty<object>()).Select(p => p.GetType()).ToArray());
Expression m;
if (memberInfo.MemberType == MemberTypes.Method) {
var a = memberArgs.Select(p => Expression.Constant(p));
m = Expression.Call(e, (MethodInfo) memberInfo, a);
}
else {
m = Expression.MakeMemberAccess(e, memberInfo);
}
var mi = m.Type.GetMethod(method, args.Select(a => a.GetType()).ToArray());
var c = args.Select(a => Expression.Constant(a, a.GetType()));
MethodCallExpression call;
if (mi != null) {
call = Expression.Call(m, mi, c);
}
else {
mi = typeof(Extensions).GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(meth => meth.Name == method);
var newArgsList = c.ToList<object>();
newArgsList.Insert(0, m);
c = newArgsList.Select(a => Expression.Constant(a, a.GetType()));
call = Expression.Call(null, mi, c);
}
return Expression.Lambda<Func<T, bool>>(call, e);
}
You have to pass a null ConsantExpression as the instance parameter of Expression.Call, and add m as the first argument in c.