Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Property selector Expression<Func<T>>. How to get/set value to selected property

Tags:

c#

I have an object that I want to be constructed in such manner:

var foo = new FancyObject(customer, c=>c.Email); //customer has Email property

How should I declare second parameter?

How the code that will access selected property setter/getter will look like?

Upd. There are several entities in the model that has Email property. So probably the signature will looks like:

public FancyObject(Entity holder, Expression<Func<T>> selector)

and the constructor call

var foo = new FancyObject(customer, ()=>customer.Email);
like image 324
v00d00 Avatar asked Feb 22 '11 07:02

v00d00


2 Answers

The parameter would be an Expression<Func<Customer,string>> selector. Reading it can be via flat compile:

 Func<Customer,string> func = selector.Compile();

then you can access func(customer). Assigning is trickier; for simple selectors your could hope that you can simply decompose to:

var prop = (PropertyInfo)((MemberExpression)selector.Body).Member;
prop.SetValue(customer, newValue, null);

But more complex expressions would either need a manual tree walk, or some of the 4.0 expression node-types:

        Expression<Func<Customer, string>> email
             = cust => cust.Email;

        var newValue = Expression.Parameter(email.Body.Type);
        var assign = Expression.Lambda<Action<Customer, string>>(
            Expression.Assign(email.Body, newValue),
            email.Parameters[0], newValue);

        var getter = email.Compile();
        var setter = assign.Compile();
like image 142
Marc Gravell Avatar answered Nov 03 '22 23:11

Marc Gravell


It looks like the type would have to be generic with two type parameters - the source and result. For example, you might use:

var foo = new FancyObject<Customer, string>(customer, c => c.Email);

The first parameter would be of type TSource, and the second would be Expression<Func<TSource, TResult>>:

public class FancyObject<TSource, TResult>
{
    private readonly TSource value;
    private readonly Expression<Func<TSource, TResult>> projection;

    public FancyObject(TSource value, 
                       Expression<Func<TSource, TResult>> projection)
    {
        this.value = value;
        this.projection = projection;
    }
}

You can make this simpler to use with a static method in a non-generic type:

var foo = FancyObject.Create(customer, c => c.Email);

That can use type inference to work out the type arguments.

like image 36
Jon Skeet Avatar answered Nov 04 '22 01:11

Jon Skeet