42

I have created methods for converting a property lambda to a delegate:

public static Delegate MakeGetter<T>(Expression<Func<T>> propertyLambda)
{
    var result = Expression.Lambda(propertyLambda.Body).Compile();
    return result;
}

public static Delegate MakeSetter<T>(Expression<Action<T>> propertyLambda)
{
    var result = Expression.Lambda(propertyLambda.Body).Compile();
    return result;
}

These work:

Delegate getter = MakeGetter(() => SomeClass.SomeProperty);
object o = getter.DynamicInvoke();

Delegate getter = MakeGetter(() => someObject.SomeProperty);
object o = getter.DynamicInvoke();

but these won't compile:

Delegate setter = MakeSetter(() => SomeClass.SomeProperty);
setter.DynamicInvoke(new object[]{propValue});

Delegate setter = MakeSetter(() => someObject.SomeProperty);
setter.DynamicInvoke(new object[]{propValue});

The MakeSetter lines fail with "The type arguments cannot be inferred from the usage. Try specifying the type arguments explicitly."

Is what I'm trying to do possible? Thanks in advance.

Jim C
  • 4,517
  • 7
  • 29
  • 33

4 Answers4

62

The Expression API supports this in .NET 4.0, but sadly the C# compiler doesn't add any extra candy to support. But the good news is that you can trivially take a "get" expression (which the C# compiler can write) and re-write it as a "set" expression.

And even better; if you don't have .NET 4.0, there are still at least two other ways of performing a "set" via an expression written as a "get".

Here they all are, for info:

using System;
using System.Linq.Expressions;
using System.Reflection;
class Foo {
    public string Bar { get; set; }
    static void Main() {
        // take a "get" from C#
        Expression<Func<Foo, string>> get = foo => foo.Bar;

        // re-write in .NET 4.0 as a "set"
        var member = (MemberExpression)get.Body;
        var param = Expression.Parameter(typeof(string), "value");
        var set = Expression.Lambda<Action<Foo, string>>(
            Expression.Assign(member, param), get.Parameters[0], param);

        // compile it
        var action = set.Compile();
        var inst = new Foo();
        action(inst, "abc");
        Console.WriteLine(inst.Bar); // show it working

        //==== reflection
        MethodInfo setMethod = ((PropertyInfo)member.Member).GetSetMethod();
        setMethod.Invoke(inst, new object[] { "def" });
        Console.WriteLine(inst.Bar); // show it working

        //==== Delegate.CreateDelegate
        action = (Action<Foo, string>)
            Delegate.CreateDelegate(typeof(Action<Foo, string>), setMethod);
        action(inst, "ghi");
        Console.WriteLine(inst.Bar); // show it working
    }
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    Maybe a silly question but why the `/` instead of `*` for the italics? – Camilo Martin Nov 03 '10 at 05:30
  • 1
    @Camilo Old BBS habits die hard – Aidan Ryan Jun 22 '11 at 14:43
  • This was very helpful to me! One subtle difference when using using Invoke on MethodInfo is that the type doesn't have to match exactly. So you could create a generate MakeSetter method and give it an Expression>. The other two ways fail at runtime if you try that. – Matthew Groves Jun 29 '12 at 18:19
  • 2
    Great answer. My own subjective performance tests show that `set.Compile()` is quite expensive, but if you can cache the delegate it produces then performance is fine. Using `CreateDelegate` is almost always quicker than `Expression`, especially if you are only going to invoke the delegate once. If you cannot cache the delegate, reflection is the best bet, and actually not much worse than even the best case when only invoking the delegate once. Lesson learned: reflection isn't as evil as you might think. – batwad Dec 19 '12 at 13:15
  • Would you know how to get this to work with 'deep' properties? So I could for example do: `Expression> get = foo => foo.Bar.Text;` – Peter Mar 18 '14 at 15:49
  • 1
    For anyone interested: I found help on how to do this for 'deep' properties (like a.b.c.d) here: http://vincentlauzon.wordpress.com/2011/06/16/expression-trees-part-3-setting-properties – Peter Mar 20 '14 at 10:27
  • Just a little tidbit - Nick Polyak at this link has produced this little gem: http://www.codeproject.com/Articles/584720/ExpressionplusbasedplusPropertyplusGettersplusandp - I posted full code as an answer- because links go dead . – Stix Oct 21 '15 at 15:51
20

As per my comments - because links go dead - I have posted the full code as an answer to the question. YES it is possible to do what the OP is requesting. and here is a nice little gem from Nick demonstrating it. Nick credits this page and another page for his complete solution along with performance metrics. I provide that below instead of just a link.

// returns property getter
public static Func<TObject, TProperty> GetPropGetter<TObject, TProperty>(string propertyName)
{
    ParameterExpression paramExpression = Expression.Parameter(typeof(TObject), "value");

    Expression propertyGetterExpression = Expression.Property(paramExpression, propertyName);

    Func<TObject, TProperty> result =
        Expression.Lambda<Func<TObject, TProperty>>(propertyGetterExpression, paramExpression).Compile();

    return result;
}

// returns property setter:
public static Action<TObject, TProperty> GetPropSetter<TObject, TProperty>(string propertyName)
{            
    ParameterExpression paramExpression = Expression.Parameter(typeof(TObject));

    ParameterExpression paramExpression2 = Expression.Parameter(typeof(TProperty), propertyName);

    MemberExpression propertyGetterExpression = Expression.Property(paramExpression, propertyName);

    Action<TObject, TProperty> result = Expression.Lambda<Action<TObject, TProperty>>
    (
        Expression.Assign(propertyGetterExpression, paramExpression2), paramExpression, paramExpression2
    ).Compile();

    return result;
}
Stix
  • 485
  • 4
  • 13
  • 1
    This is nice. The only non-stupid-delegate solution on SO. You're my hero ;-) I knew it must be possible to do it purely with expressions but I oversaw the `Assign`. – t3chb0t Dec 31 '16 at 06:06
4

Action<T> represents a delegate that takes one parameter of type T and returns nothing. The lambda expressions you provide to MakeSetter represent delegates that take no parameter and return either SomeClass.SomeProperty or someObject.SomeProperty.

The error messages you're getting are due to the fact that the compiler cannot infer the types from the lambda expressions you're passing into the MakeSetter method because what you've passed and what the method is expecting are not in sync.

Restore the Data Dumps
  • 38,967
  • 12
  • 96
  • 122
4

Your MakeSetter is expecting an Action<T> and you are passing it a Func<T> (() => someObject.SomeProperty). Try the following:

Delegate setter = MakeSetter((prop) => {someObject.SomeProperty = prop;});
setter.DynamicInvoke(new object[]{propValue});

EDIT Doesn't look like you can convert statement lambdas into expressions. This is somewhat of a round about way to do it without expressions - straight to delegates:

class Test2 {
    delegate void Setter<T>(T value);

    public static void Test() {
        var someObject = new SomeObject();
        Setter<string> setter = (v) => { t.SomeProperty = v; };
        setter.DynamicInvoke(new object[]{propValue});
    }
}
Igor Zevaka
  • 74,528
  • 26
  • 112
  • 128
  • I get "A lambda expression with a statement body cannot be converted to an expression tree". – Jim C May 12 '10 at 22:54
  • 1
    Your link "convert statement lambdas into expressions" is set to your local host: http://127.0.0.1:47873/help/1-3644/ms.help?product=VS&productVersion=100&method=f1&query=CS0834%00vs.output&locale=en-US&category=TargetFrameworkMoniker%3a.NETFramework,Version%3Dv4.0 – elpezganzo Oct 11 '18 at 03:59