Search code examples
c#expression-trees

Assign instance member field in expression-trees


I try to modify this answer by making the class instance a parameter. The main idea is to create a setter for a class member no matter if it is a field or a property. I was successful with properties but got stuck with fields. This is the relevant part of the original code:

 public static Action<T> ToSetter<T>(Expression<Func<T>> expr) {
    var memberExpression = (MemberExpression)expr.Body;
    var instanceExpression = memberExpression.Expression;
    var parameter = Expression.Parameter(typeof(T));

    // assuming memberExpression.Member is FieldInfo;
    return Expression.Lambda<Action<T>>(Expression.Assign(memberExpression, parameter), parameter).Compile();        
}

Application:

 var setter= ToSetter<string>(() => myClient.WorkPhone);
 setter("12345");

This is what I want to have:

 public static Action<O,T> ToSetter<T,O>(Expression<Func<O,T>> expr) where O : class {
    var memberExpression = (MemberExpression)expr.Body;
    var instance = Expression.Parameter(typeof(O));
    var parameter = Expression.Parameter(typeof(T));

    // the following throws an InvalidOperationException exception:
    return Expression.Lambda<Action<O,T>>(
        Expression.Assign(memberExpression, parameter), parameter).Compile(); 
}

Application:

var setter= ToSetter<Client,string>(c=> c.WorkPhone);
setter(myClient, "12345");

How do I have to modify Expression.Lambda<Action<O,T>>(Expression.Assign(memberExpression, parameter),instance, parameter).Compile() to consider the instance of class O?


Solution

  • Try this:

        // Example usage: ToSetter<MyEntity, string>(c => c.FirstName)
        public static Action<TEntity, TResult> ToSetter<TEntity, TResult>(Expression<Func<TEntity, TResult>> expr)
        {
            // This will be `c.FirstName`
            var memberExpression = (MemberExpression)expr.Body;
            // This will be `c`
            var instanceParameter = (ParameterExpression)memberExpression.Expression;
            // New parameter for passing value named `value`
            var valueParameter = Expression.Parameter(typeof(TResult), "value");
    
            // Construct `(c, value) => c.FirstName = value`
            return Expression.Lambda<Action<TEntity, TResult>>(
                Expression.Assign(memberExpression, valueParameter), // c.FirstName = value
                instanceParameter, // c
                valueParameter // value
            ).Compile();        
        }
    

    What you have been missing is second parameter for Lambda call. Also, memberExpression has some parameter inside from original lambda, and it must be the same as in new lambda.