how to create expression tree / lambda for a deep property from a string

c# expression-trees lambda

Question

Given a string: "Person.Address.Postcode" I want to be able to get/set this postcode property on an instance of Person. How can I do this? My idea was to split the string by "." and then iterate over the parts, looking for the property on the previous type, then build up an expression tree that would look something like (apologies for the pseudo syntax):

(person => person.Address) address => address.Postcode

I'm having real trouble acutally creating the expression tree though! If this is the best way, can someone suggest how to go about it, or is there an easier alternative?

Thanks

Andrew

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

    public Person()
    {
        Address = new Address();
    }
}

public class Address 
{
    public string Postcode { get; set; }
}

Accepted Answer

Why you don't use recursion? Something like:

setProperyValue(obj, propertyName, value)
{
  head, tail = propertyName.SplitByDotToHeadAndTail(); // Person.Address.Postcode => {head=Person, tail=Address.Postcode}
  if(tail.Length == 0)
    setPropertyValueUsingReflection(obj, head, value);
  else
    setPropertyValue(getPropertyValueUsingReflection(obj, head), tail, value); // recursion
}

Expert Answer

It sounds like you're sorted with regular reflection, but for info, the code to build an expression for nested properties would be very similar to this order-by code.

Note that to set a value, you need to use GetSetMethod() on the property and invoke that - there is no inbuilt expression for assigning values after construction (although it is supported in 4.0).

(edit) like so:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class Foo
{
    public Foo() { Bar = new Bar(); }
    public Bar Bar { get; private set; }
}
class Bar
{
    public string Name {get;set;}
}
static class Program
{
    static void Main()
    {
        Foo foo = new Foo();
        var setValue = BuildSet<Foo, string>("Bar.Name");
        var getValue = BuildGet<Foo, string>("Bar.Name");
        setValue(foo, "abc");
        Console.WriteLine(getValue(foo));        
    }
    static Action<T, TValue> BuildSet<T, TValue>(string property)
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        ParameterExpression valArg = Expression.Parameter(typeof(TValue), "val");
        Expression expr = arg;
        foreach (string prop in props.Take(props.Length - 1))
        {
            // use reflection (not ComponentModel) to mirror LINQ 
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        // final property set...
        PropertyInfo finalProp = type.GetProperty(props.Last());
        MethodInfo setter = finalProp.GetSetMethod();
        expr = Expression.Call(expr, setter, valArg);
        return Expression.Lambda<Action<T, TValue>>(expr, arg, valArg).Compile();        

    }
    static Func<T,TValue> BuildGet<T, TValue>(string property)
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach (string prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ 
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        return Expression.Lambda<Func<T, TValue>>(expr, arg).Compile();
    }
}



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why