I'm new to expressions, and i'd like to know how if it's in any way possible to convert my expression
Let's say in this example my TModel is of type Customer, and assigned it somewhere like this:
Expression<Func<TModel, string>> getvalueexpression = customer =>customer.Name
to something like
Expression<Action<TModel,string>> setvalueexpression = [PSEUDOCODE] getvalueexpression = input
Action<TModel,string> Setter = setvalueexpression.Compile();
Setter(mycustomer,value);
So in short, i want to somehow build and compile an expression that sets the customer name specified by my getter expression, to a specific value.
static Expression<Action<T, TProperty>> MakeSetter<T, TProperty>(Expression<Func<T, TProperty>> getter)
{
var memberExpr = (MemberExpression)getter.Body;
var @this = Expression.Parameter(typeof(T), "$this");
var value = Expression.Parameter(typeof(TProperty), "value");
return Expression.Lambda<Action<T, TProperty>>(
Expression.Assign(Expression.MakeMemberAccess(@this, memberExpr.Member), value),
@this, value);
}
I have this helper method which returns the property info for a property:
public static PropertyInfo GetPropertyInfo<T, U>(Expression<Func<T, U>> property) where T : class
{
var memberExpression = (property.Body as MemberExpression);
if (memberExpression != null && memberExpression.Member is PropertyInfo)
{
return memberExpression.Member as PropertyInfo;
}
throw new InvalidOperationException("Invalid usage of GetPropertyInfo");
}
Usage: GetPropertyInfo((MyClass c) => c.PropertyName);
You can then use the PropertyInfo to set the value of the property on a class.
You will need to modify the code to suit your needs but hopefully it will help.
Modified version. This class is probably better than many other ones you can find around :-) This is because this version support direct properties (p => p.B
) (as everyone else :-) ), nested properties (p => p.B.C.D
), fields (both "terminal" and "in the middle", so in p => p.B.C.D
both B
and D
could be fields) and "inner" casting of types (so p => ((BType)p.B).C.D
and p => (p.B as BType).C.D)
. The only thing that isn't supported is casting of the "terminal" element (so no p => (object)p.B
).
There are two "codepaths" in the generator: for simple Expressions (p => p.B
) and for "nested" expressions. There are code variants for .NET 4.0 (that has the Expression.Assign
expression type). From some benchmarks of mine the fastest delegates are: "simple" Delegate.CreateDelegate
for properties, Expression.Assign
for fields and "simple" FieldSetter
for fields (this one is just a little slower than Expression.Assign
for fields). So under .NET 4.0 you should take away all the code marked as 3.5.
Part of the code isn't mine. The initial (simple) version was based on the Fluent NHibernate code (but it supported only direct properties), some other parts are based on code from How do I set a field value in an C# Expression tree? and Assignment in .NET 3.5 expression trees.
public static class FluentTools
{
public static Action<T, TValue> GetterToSetter<T, TValue>(Expression<Func<T, TValue>> getter)
{
ParameterExpression parameter;
Expression instance;
MemberExpression propertyOrField;
GetMemberExpression(getter, out parameter, out instance, out propertyOrField);
// Very simple case: p => p.Property or p => p.Field
if (parameter == instance)
{
if (propertyOrField.Member.MemberType == MemberTypes.Property)
{
// This is FASTER than Expression trees! (5x on my benchmarks) but works only on properties
PropertyInfo property = propertyOrField.Member as PropertyInfo;
MethodInfo setter = property.GetSetMethod();
var action = (Action<T, TValue>)Delegate.CreateDelegate(typeof(Action<T, TValue>), setter);
return action;
}
#region .NET 3.5
else // if (propertyOrField.Member.MemberType == MemberTypes.Field)
{
// 1.2x slower than 4.0 method, 5x faster than 3.5 method
FieldInfo field = propertyOrField.Member as FieldInfo;
var action = FieldSetter<T, TValue>(field);
return action;
}
#endregion
}
ParameterExpression value = Expression.Parameter(typeof(TValue), "val");
Expression expr = null;
#region .NET 3.5
if (propertyOrField.Member.MemberType == MemberTypes.Property)
{
PropertyInfo property = propertyOrField.Member as PropertyInfo;
MethodInfo setter = property.GetSetMethod();
expr = Expression.Call(instance, setter, value);
}
else // if (propertyOrField.Member.MemberType == MemberTypes.Field)
{
expr = FieldSetter(propertyOrField, value);
}
#endregion
//#region .NET 4.0
//// For field access it's 5x faster than the 3.5 method and 1.2x than "simple" method. For property access nearly same speed (1.1x faster).
//expr = Expression.Assign(propertyOrField, value);
//#endregion
return Expression.Lambda<Action<T, TValue>>(expr, parameter, value).Compile();
}
private static void GetMemberExpression<T, U>(Expression<Func<T, U>> expression, out ParameterExpression parameter, out Expression instance, out MemberExpression propertyOrField)
{
Expression current = expression.Body;
while (current.NodeType == ExpressionType.Convert || current.NodeType == ExpressionType.TypeAs)
{
current = (current as UnaryExpression).Operand;
}
if (current.NodeType != ExpressionType.MemberAccess)
{
throw new ArgumentException();
}
propertyOrField = current as MemberExpression;
current = propertyOrField.Expression;
instance = current;
while (current.NodeType != ExpressionType.Parameter)
{
if (current.NodeType == ExpressionType.Convert || current.NodeType == ExpressionType.TypeAs)
{
current = (current as UnaryExpression).Operand;
}
else if (current.NodeType == ExpressionType.MemberAccess)
{
current = (current as MemberExpression).Expression;
}
else
{
throw new ArgumentException();
}
}
parameter = current as ParameterExpression;
}
#region .NET 3.5
// Based on https://stackoverflow.com/questions/321650/how-do-i-set-a-field-value-in-an-c-expression-tree/321686#321686
private static Action<T, TValue> FieldSetter<T, TValue>(FieldInfo field)
{
DynamicMethod m = new DynamicMethod("setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(FluentTools));
ILGenerator cg = m.GetILGenerator();
// arg0.<field> = arg1
cg.Emit(OpCodes.Ldarg_0);
cg.Emit(OpCodes.Ldarg_1);
cg.Emit(OpCodes.Stfld, field);
cg.Emit(OpCodes.Ret);
return (Action<T, TValue>)m.CreateDelegate(typeof(Action<T, TValue>));
}
// Based on https://stackoverflow.com/questions/208969/assignment-in-net-3-5-expression-trees/3972359#3972359
private static Expression FieldSetter(Expression left, Expression right)
{
return
Expression.Call(
null,
typeof(FluentTools)
.GetMethod("AssignTo", BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(left.Type),
left,
right);
}
private static void AssignTo<T>(ref T left, T right) // note the 'ref', which is
{ // important when assigning
left = right; // to value types!
}
#endregion
}
As the correct answer didn't work for me (collections in the expression) but pushed me to the right direction, I needed to investigate this a lot and I think I came up with a method which can generate setter for literally any member expression.
For properties and fields it behaves the same as the marked answer (I believe it is much more transparent though).
It has additional support for Lists and Dictionaries - please see in the comments.
public static Action<TObject, TPropertyOnObject> GetSetter<TObject, TPropertyOnObject>(Expression<Func<TObject, TPropertyOnObject>> getterExpression)
{
/*** SIMPLE PROPERTIES AND FIELDS ***/
// check if the getter expression refers directly to a PROPERTY or FIELD
var memberAcessExpression = getterExpression.Body as MemberExpression;
if (memberAcessExpression != null)
{
//to here we assign the SetValue method of a property or field
Action<object, object> propertyOrFieldSetValue = null;
// property
var propertyInfo = memberAcessExpression.Member as PropertyInfo;
if (propertyInfo != null)
{
propertyOrFieldSetValue = (declaringObjectInstance, propertyOrFieldValue) => propertyInfo.SetValue(declaringObjectInstance, propertyOrFieldValue);
};
// field
var fieldInfo = memberAcessExpression.Member as FieldInfo;
if (fieldInfo != null)
{
propertyOrFieldSetValue = (declaringObjectInstance, propertyOrFieldValue) => fieldInfo.SetValue(declaringObjectInstance, propertyOrFieldValue);
}
// This is the expression to get declaring object instance.
// Example: for expression "o=>o.Property1.Property2.CollectionProperty[3].TargetProperty" it gives us the "o.Property1.Property2.CollectionProperty[3]" part
var memberAcessExpressionCompiledLambda = Expression.Lambda(memberAcessExpression.Expression, getterExpression.Parameters.Single()).Compile();
Action<TObject, TPropertyOnObject> setter = (expressionParameter, value) =>
{
// get the object instance on which is the property we want to set
var declaringObjectInstance = memberAcessExpressionCompiledLambda.DynamicInvoke(expressionParameter);
Debug.Assert(propertyOrFieldSetValue != null, "propertyOrFieldSetValue != null");
// set the value of the property
propertyOrFieldSetValue(declaringObjectInstance, value);
};
return setter;
}
/*** COLLECTIONS ( IDictionary<,> and IList<,>) ***/
/*
* DICTIONARY:
* Sample expression:
* "myObj => myObj.Property1.ListProperty[5].AdditionalInfo["KEY"]"
* Setter behaviour:
* The same as adding to a dictionary.
* It does Add("KEY", <value to be set>) to the dictionary. It fails if the jey already exists.
*
*
* LIST
* Sample expression:
* "myObj => myObj.Property1.ListProperty[INDEX]"
* Setter behaviour:
* If INDEX >= 0 and the index exists in the collection it behaves the same like inserting to a collection.
* IF INDEX < 0 (is negative) it adds the value at the end of the collection.
*/
var methodCallExpression = getterExpression.Body as MethodCallExpression;
if (
methodCallExpression != null && methodCallExpression.Object != null &&
methodCallExpression.Object.Type.IsGenericType)
{
var collectionGetterExpression = methodCallExpression.Object as MemberExpression;
Debug.Assert(collectionGetterExpression != null, "collectionGetterExpression != null");
// This gives us the collection instance when it is invoked on the object instance whic the expression is for
var collectionGetterCompiledLambda =Expression.Lambda(collectionGetterExpression, getterExpression.Parameters.Single()).Compile();
// this returns the "KEY" which is the key (object) in case of Dictionaries and Index (integer) in case of other collections
var collectionKey = ((ConstantExpression) methodCallExpression.Arguments[0]).Value;
var collectionType = collectionGetterExpression.Type;
// IDICTIONARY
if (collectionType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
// Create an action which accepts the instance of the object which the "dictionary getter" expression is for and a value
// to be added to the dictionary.
Action<TObject, TPropertyOnObject> dictionaryAdder = (expressionParameter, value) =>
{
try
{
var dictionaryInstance = (IDictionary)collectionGetterCompiledLambda.DynamicInvoke(expressionParameter);
dictionaryInstance.Add(collectionKey, value);
}
catch (Exception exception)
{
throw new Exception(
string.Format(
"Addition to dictionary failed [Key='{0}', Value='{1}']. The \"adder\" was generated from getter expression: '{2}'.",
collectionKey,
value,
getterExpression.ToString()), exception);
}
};
return dictionaryAdder;
}
// ILIST
if (typeof (IList<>).MakeGenericType(typeof (bool)).IsAssignableFrom(collectionType.GetGenericTypeDefinition().MakeGenericType(typeof(bool))))
{
// Create an action which accepts the instance of the object which the "collection getter" expression is for and a value
// to be inserted
Action<TObject, TPropertyOnObject> collectionInserter = (expressionParameter, value) =>
{
try
{
var collectionInstance = (IList<TPropertyOnObject>)collectionGetterCompiledLambda.DynamicInvoke(expressionParameter);
var collectionIndexFromExpression = int.Parse(collectionKey.ToString());
// The semantics of a collection setter is to add value if the index in expression is <0 and set the item at the index
// if the index >=0.
if (collectionIndexFromExpression < 0)
{
collectionInstance.Add(value);
}
else
{
collectionInstance[collectionIndexFromExpression] = value;
}
}
catch (Exception invocationException)
{
throw new Exception(
string.Format(
"Insertion to collection failed [Index='{0}', Value='{1}']. The \"inserter\" was generated from getter expression: '{2}'.",
collectionKey,
value,
getterExpression.ToString()), invocationException);
}
};
return collectionInserter;
}
throw new NotSupportedException(
string.Format(
"Cannot generate setter from the given expression: '{0}'. Collection type: '{1}' not supported.",
getterExpression, collectionType));
}
throw new NotSupportedException("Cannot generate setter from the given expression: "+getterExpression);
}
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