Creating a lambda action from a function expression

It is relatively easy to create a lambda function that will return the value of a property from an object, even including deep properties ...

Func<Category, string> getCategoryName = new Func<Category, string>(c => c.Name); 

and it can be called as follows:

 string categoryName = getCategoryName(this.category); 

But, given only the function obtained above (or the expression originally used to create the function), can anyone provide an easy way to create the opposite action ...

Action<Category, string> setCategoryName = new Action<Category, string>((c, s) => c.Name = s);

... which allows you to set the same property value as follows:

 setCategoryName(this.category, ""); 

Note that I'm looking for a way to programmatically create an action from a function or expression - I hope that I showed that I already know how to create it manually.

I am open to answers that work in both .net 3.5 and 4.0.

Thanks.

UPDATE:

Perhaps it is not clear to me in my question, so let me try to demonstrate more clearly what I'm trying to do.

I have the following method (which I created for the purpose of this question) ...

 void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { Func<TObject, TValue> getValue = expression.Compile(); TValue stuff = getValue(obj); Expression<Action<TObject, TValue>> assignmentExpression = (o, v) => Expression<TObject>.Assign(expression, Expression.Constant(v, typeof(TValue))); Action<TObject, TValue> setValue = assignmentExpression.Compile(); setValue(obj, stuff); } 

I'm looking for how to create an “assignment assignment” in code so that I can compile it into setValue? I believe this is due to Expression.Assign, but I just can't work out the right combination of parameters to complete the code.

The end result is the ability to call

 Category category = *<get object from somewhere>*; this.DoLambdaStuff(category, c => c.Name); 

and this, in turn, will create a getter and setter for the Name property of the Category object.

The above version compiles, but when I call setValue (), it results in an ArgumentException with the expression “Expression must be written”.

Thanks again.

+7
lambda
source share
4 answers
 void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { Func<TObject, TValue> getValue = expression.Compile(); TValue stuff = getValue(obj); var p = Expression.Parameter(typeof(TValue), "v"); Expression<Action<TObject, TValue>> assignmentExpression = Expression.Lambda<Action<TObject, TValue>>(Expression.Assign(expression.Body, p), expression.Parameters[0], p); Action<TObject, TValue> setValue = assignmentExpression.Compile(); setValue(obj, stuff); } 
+5
source share

Ok, the code I'm looking for looks something like this:

 ParameterExpression objectParameterExpression = Expression.Parameter( typeof(TObject)), valueParameterExpression = Expression.Parameter(typeof(TValue) ); Expression<Action<TObject, TValue>> setValueExpression = Expression.Lambda<Action<TObject, TValue>>( Expression.Block( Expression.Assign( Expression.Property( objectParameterExpression, ((MemberExpression) expression.Body).Member.Name ), valueParameterExpression ) ), objectParameterExpression, valueParameterExpression ); Action<TObject, TValue> setValue = setValueExpression.Compile(); 

This code works, but only for shallow properties (that is, properties of the immediate object), but does not work for deep properties, although the getter function can work for deep properties. It would be interesting to know if anyone can help me change this to work with deep properties, but I will raise this as a separate issue.

+3
source share

As Martin noted, “deep” properties violate his decision. The reason this does not work is the expression:

 Expression.Property( objectParameterExpression , ((MemberExpression)expression.Body).Member.Name ) 

The reason for this is not immediately obvious: the derived class declares its own getter property, which is redirected to the base class, but does not define the corresponding setter.

To get around this problem, you need to find the property through reflection, look for its type of declaration, and then get the corresponding property from the declaration. Unlike the property that you get from the derived class, the property from the declaration has getter and setter and is therefore assigned. (It is assumed that the property definition tool is declared at all: if the declarant does not provide a setter, then no one can provide it.)

Here is a skeletal implementation that solves this problem. Replace the above call with Expression.Property next call to GetPropertyOrField , and Martin's solution will work.

 private static MemberExpression GetPropertyOrField(Expression baseExpr, string name) { if (baseExpr == null) { throw new ArgumentNullException("baseExpr"); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name"); } var type = baseExpr.Type; var properties = type .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(p => p.Name.Equals(name)) .ToArray(); if (properties.Length == 1) { var res = properties[0]; if (res.DeclaringType != type) { // Here is the core of the fix: var tmp = res .DeclaringType .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(p => p.Name.Equals(name)) .ToArray(); if (tmp.Length == 1) { return Expression.Property(baseExpr, tmp[0]); } } return Expression.Property(baseExpr, res); } if (properties.Length != 0) { throw new NotSupportedException(name); } var fields = type .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(p => p.Name.Equals(name)) .ToArray(); if (fields.Length == 1) { return Expression.Field(baseExpr, fields[0]); } if (fields.Length != 0) { throw new NotSupportedException(name); } throw new ArgumentException( string.Format( "Type [{0}] does not define property/field called [{1}]" , type , name ) ); } 
+2
source share

This should be possible using expression trees that can be created from lambda expressions, modified, and then compiled into a delegate.

0
source share

All Articles