80

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);
Oren Hizkiya
  • 4,420
  • 2
  • 23
  • 33
v00d00
  • 3,215
  • 3
  • 32
  • 43

2 Answers2

130

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();
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thank you Marc, I think this will be anough, asumng class will have generic type of {public class FancyObject} as Jon suggested. – v00d00 Feb 22 '11 at 08:10
  • 2
    @Neo no, that is not "more correct" - it is just "less direct"; sorry, but I understand the `Expression` API very well. I could settle for `email.Parameters.Single()`, but: the version above is fine. – Marc Gravell Oct 10 '12 at 12:34
  • I'm trying to use your code with `Expression.Lambda>` (as opposed to `string`) so that I can use the code generically on a colletion of expression-pairs of various types. However, I'm getting the error "ParameterExpression of type 'System.String' cannot be used for delegate parameter of type 'System.Object'" when the type of property is a `string`. I guess this would also happen for any other type other than `object`. Any idea how to make this code work for such a situation? – Neo Oct 10 '12 at 12:56
  • @Neo you'll have to use Expression.Convert, which is a general purpose cast – Marc Gravell Oct 10 '12 at 14:01
  • Yes, I found this: http://stackoverflow.com/questions/10760139 But, I couldn't get it to work, so I've just raised this question: http://stackoverflow.com/questions/12821323 Would be very grateful if you could have a look... :) – Neo Oct 10 '12 at 14:09
  • OK, that'd be great as I've been trying all sorts using Expression.Convert and just can't get it to work. – Neo Oct 10 '12 at 17:12
  • @MarcGravell, I know it is entirely off-topic for this thread, but still... `prop.SetValue(customer, newValue, null);` - is there any way to get `customer` (i.e. the target object) entirely from the expression so that value can be set to the `customer` object without having to pass `customer` as a parameter explicitly? – Sнаđошƒаӽ Mar 25 '18 at 08:54
  • @Sнаđошƒаӽ would need a proper example (i.e. that shows context); in the code shown here, there is no `prop.SetValue` and the target is a parameter (so doesn't exist in isolation). The answer to your question is probably "yes", but without an example, it is impossible to show *how* – Marc Gravell Mar 25 '18 at 13:44
  • Follow up question: If all you're after is a read-only property selector, all you need is the Func, right? No need for an `Expression`? – xr280xr Apr 19 '21 at 21:48
  • @xrw80xr yes, if you just need to be able to invoke it and get a result (instead of inspect it and understand the intent): you don't need expression trees – Marc Gravell Apr 20 '21 at 07:07
  • 1
    Tip for future readers, you should cache that compiled expression somehow, because compiling expressions on the fly *will* incur a performance penalty. – Bruno Brant May 19 '21 at 14:47
7

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks. And how about accessing the selected property? – v00d00 Feb 22 '11 at 08:02
  • @jonskeet just out of interest in this instance how would use the projection? I'm interested as I understood you could do a similar thing with just a Func and call Func.Invoke() to get the value. – MrEdmundo Jul 04 '13 at 11:01
  • @MrEdmundo: Well you could use it in LINQ to SQL, for example... where you can't use a Func. – Jon Skeet Jul 04 '13 at 11:15
  • 1
    @JonSkeet thanks, and in this circumstance, what's the right way to execute the expression (I'm new to Expressions). – MrEdmundo Jul 04 '13 at 11:18
  • @MrEdmundo: It entirely depends on what you're trying to do with it, to be honest. That's too broad a question for me to answer. – Jon Skeet Jul 04 '13 at 11:37