Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create lambda action from function expression

Tags:

.net

lambda

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 this can be called as follows...

string categoryName = getCategoryName(this.category);

But, given only the resulting function above (or the expression originally used to create the function), can anybody provide an easy way to create the opposing action...

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

...that will enable the same property value to be set as follows?

setCategoryName(this.category, "");

Note that I am looking for a way to create the action programatically from the function or expression - I hope that I have shown 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 I am not being clear in my question, so let me try and demonstrate more clearly what I am trying to do.

I have the following method (that I have created for the purposes 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);

}

What I am looking for is how do I create the "assignmentExpression" within the code so that I can compile it into setValue? I figure it is related to Expression.Assign, but I simply cannot work out the correct combination of parameters to complete the code.

The eventual result is to be able to call

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

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

The version above compiles, but when I call setValue() it results in an ArgumentException with "Expression must be writeable".

Thanks again.

like image 883
Martin Robins Avatar asked May 15 '10 16:05

Martin Robins


People also ask

How do you convert this method into a lambda expression?

To convert an anonymous method to a lambda expressionMove to the anonymous method you want to convert. From the Refactor menu of the VisualAid choose To Lambda. Telerik® JustCode™ will replace the anonymous method with a lambda expression.

What does [=] mean in lambda function?

You can use a capture-default mode to indicate how to capture any outside variables referenced in the lambda body: [&] means all variables that you refer to are captured by reference, and [=] means they're captured by value.


2 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);
}
like image 125
user3018853 Avatar answered Sep 18 '22 15:09

user3018853


Ok, the code I am looking for goes 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 anybody can help me modify this to work with deep properties but I will raise this as a seperate question.

like image 45
Martin Robins Avatar answered Sep 20 '22 15:09

Martin Robins