Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create an Action<T> to "set" a property, when I am provided with the LINQ Expression for the "get"

I'd like to be able to generate a compiled expression to set a property, given the lambda expression that provides the "get" method for a property.

Here's what I'm looking for:

public Action<int> CreateSetter<T>(Expression<Func<T, int>> getter)
{
    // returns a compiled action using the details of the getter expression tree, or null
    // if the write property is not defined.
}

I'm still trying to understand the various types of Expression classes, so if you can point me in the right direction that would be great.

like image 940
Alex Avatar asked Jan 04 '11 17:01

Alex


2 Answers

Using @Ani's answer as a starting point, you can use the following to generate a compiled expression.

[TestMethod]
public void CreateSetterFromGetter()
{
    Action<Person, int> ageSetter = InitializeSet((Person p) => p.Age);
    Action<Person, string> nameSetter = InitializeSet((Person p) => p.Name);

    Person p1 = new Person();
    ageSetter(p1, 29);
    nameSetter(p1, "John");

    Assert.IsTrue(p1.Name == "John");
    Assert.IsTrue(p1.Age == 29);
}

public class Person { public int Age { get; set; } public string Name { get; set; } }

public static Action<TContainer, TProperty> InitializeSet<TContainer, TProperty>(Expression<Func<TContainer, TProperty>> getter)
{
    PropertyInfo propertyInfo = (getter.Body as MemberExpression).Member as PropertyInfo;

    ParameterExpression instance = Expression.Parameter(typeof(TContainer), "instance");
    ParameterExpression parameter = Expression.Parameter(typeof(TProperty), "param");

    return Expression.Lambda<Action<TContainer, TProperty>>(
        Expression.Call(instance, propertyInfo.GetSetMethod(), parameter),
        new ParameterExpression[] { instance, parameter }).Compile();
}

You should cache the compiled expression to keep it handy for multiple uses.

like image 74
Adam Spicer Avatar answered Oct 02 '22 02:10

Adam Spicer


You could of course walk the expression-tree and then use Delegate.CreateDelegate to create the appropriate Action<,>. It's quite simple, except for all of the validation-checks (I'm unsure if I've covered everything):

I'm no expression-tree expert, but I don't think building an expression-tree and then calling Compile is possible here since expression-trees can't contain assignment statements, as far as I know. (EDIT: Apparently, these have been added in .NET 4. It's a hard-to-find feature since the C# compiler doesn't seem to be able to build them from lambdas).

public static Action<TContaining, TProperty>
    CreateSetter<TContaining, TProperty>
    (Expression<Func<TContaining, TProperty>> getter)
{
    if (getter == null)
        throw new ArgumentNullException("getter");

    var memberEx = getter.Body as MemberExpression;

    if (memberEx == null)
        throw new ArgumentException("Body is not a member-expression.");

    var property = memberEx.Member as PropertyInfo;

    if (property == null)
        throw new ArgumentException("Member is not a property.");

    if(!property.CanWrite)
        throw new ArgumentException("Property is not writable.");

    return (Action<TContaining, TProperty>)
           Delegate.CreateDelegate(typeof(Action<TContaining, TProperty>),
                                   property.GetSetMethod());
}

Usage:

public class Person { public int Age { get; set; } }

...

static void Main(string[] args)
{
    var setter = CreateSetter((Person p) => p.Age);
    var person = new Person();
    setter(person, 25);

    Console.WriteLine(person.Age); // 25     
}

Do note that this creates an open instance delegate, meaning that it's not bound to any particular instance of TContaining. It's simple to modify it to be bound to a specific instance; you'll have to pass a TContaining as well to the method and then use a different overload of Delegate.CreateDelegate. The signature of the method would then look something like:

public static Action<TProperty> CreateSetter<TContaining, TProperty>
        (Expression<Func<TContaining, TProperty>> getter, TContaining obj)
like image 40
Ani Avatar answered Oct 02 '22 02:10

Ani