7

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.

1
  • I dont really understand what you mean by doing it automatically opposed to manually. However, if you want to set properties and decide which property you want to set at runtime, you should use reflection. Moreover, you can use expression tree to build run-time lambda expressions. Commented May 15, 2010 at 16:27

4 Answers 4

5
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);
}
Sign up to request clarification or add additional context in comments.

1 Comment

+1. Using this code I am able to set values for private fields and nested private properties as well. The type of field/property could be a struct as well and it just works. Would really love an explanation of how this works.
3

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.

1 Comment

This code only works in .NET 4 as it requires the improved support for expression trees introduced in in .NET 4.
2

As Martin noted above, "deep" properties break his solution. The reason why it does not work is this expression:

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

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

In order to work around this issue, you need to locate the property through reflection, look for its declaring type, and then get the corresponding property from the declarer. Unlike the property you get on the derived class, the property from the declarer has a getter and a setter, and is therefore assignable. (This assumes that a setter of the property is declared at all: if the declarer does not provide a setter, then nobody else could possibly provide it.)

Here is a skeletal implementation that addresses this problem. Replace the above call to Expression.Property with the call of GetPropertyOrField below, and Martin's solution is going to 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
        )
    );
}

Comments

0

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

1 Comment

This is the sort of thing I was looking for, I was just hoping for an example!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.