Updating nested property value via a string path using expression

Viewed 458

The Problem to solve

I want to be able to update the property prop using the fictive method Update. To do so, i would like to call the method like this:

Root obj = /* ... */;
Update(obj, "sub/sub/prop", "foobar");

How would i eg. build some Expression tree to do this?

Scenario

class Sub2
{
    public string prop { get; set; }
}
class Sub1
{
    public Sub2 sub { get; set; }
}
class Root
{
    public Sub1 sub { get; set; }
}
class Main
{
    //...
    void Update(object obj, string navigation, object newval) { /* magic */ }
}

Full Problem

I need to be able to serialize single fields from some object (already solved, method head public void Serialize<TProperty>(T obj, Stream s, Expression<Func<T, TProperty>> exp)) and update a corresponding field on a server application. Only that field is allowed to be updated, some classes are nested way too deep to allow for solutions like "just use some ID stuff and a switch to then put the value into the right field" which is why this approach was chosen.

2 Answers

You can use recursion to navigate down to the property to update. This code expects all the properties along the path to be not NULL, if you can have nulls then it should be easy to have some checking code to handle this case (throw an Exception, etc.).

void Update(object obj, string navigation, object newval)
{
    var firstSlash = navigation.IndexOf("/");
    if (firstSlash < 0)
    {
        obj.GetType().GetProperty(navigation).SetValue(obj, newval);
    }
    else
    {
        var header = navigation.Substring(0, firstSlash);
        var tail = navigation.Substring(firstSlash + 1);
        var subObj = obj.GetType().GetProperty(header).GetValue(obj);
        Update(subObj, tail, newval);
    }
}

Just got it finally solved myself

public void Update1(T obj, string[] input, object newval)
{
    Type t = typeof(T);
    var param1 = Expression.Parameter(t);
    Expression exp = param1;
    foreach (var it in input.Skip(1).Take(input.Length - 2))
    {
        var minfo = t.GetProperty(it).GetGetMethod();
        exp = Expression.Call(exp, minfo);
        t = minfo.ReturnType;
    }
    var lastprop = t.GetProperty(input.Last());
    var minfoset = lastprop.GetSetMethod();
    var variableexp = Expression.Variable(lastprop.PropertyType);
    exp = Expression.Call(exp, minfoset, variableexp);
    var lambda = Expression.Lambda(exp, param1, variableexp);
    lambda.Compile().DynamicInvoke(obj, newval);
}
Related