How to use an Expression to set a nested property?

.net c# expression-trees reflection

Question

So I have some code that sets a property on an object. This code is from an internal validation class that we're using in unit tests. So the code may be supplied something like

private static void SetDeepValue(object targetObject, Expression<Func<string>> propertyToSet, object valueToSet)
        {
            var underlyingProperty = ((PropertyInfo)((MemberExpression)propertyToSet.Body).Member);
            underlyingProperty.SetValue(targetObject, valueToSet);
        }

This code gets used in a unit-test type environment, where we can then make calls like

foreach (string currentTestCaseValue in TestCaseSets)
{
     BusinessObject myCustomer = new BusinessObject();
     SetDeepValue(myCustomer, ()=>myCustomer.FirstName,currentTestCaseValue);
     ValidateBusinessRules(myCustomer);
}

(code simplified for brevity/complexity)

However, now, due to some refactorings, we are left with something like:

foreach (string currentTestCaseValue in TestCaseSets)
    {
         BusinessObject myCustomer = new BusinessObject();
         SetDeepValue(myCustomer, ()=>myCustomer.NameInfo.First,currentTestCaseValue);
         ValidateBusinessRules(myCustomer);
    }

When this code runs, we get the error:

Object does not match target type.

I suspect it is trying to call the First property on the BusinessObject, instead of the NameInfo. How can I modify my code to handle this 'nested' case?

Popular Answer

Now that you gave us an example, it is quite easy to do it. It would be useless to compile the expression in any way, because we can't reuse it, so it would only slow down the method. Easier to walk the "chain" of getters and use reflection to access their value. The method I wrote supports both fields (often used as readonly fields) and properties.

public static void SetDeepValue<T>(object notUsed, Expression<Func<T>> propertyToSet, T valueToSet)
{
    List<MemberInfo> members = new List<MemberInfo>();

    Expression exp = propertyToSet.Body;
    ConstantExpression ce = null;

    // There is a chain of getters in propertyToSet, with at the 
    // beginning a ConstantExpression. We put the MemberInfo of
    // these getters in members and the ConstantExpression in ce

    while (exp != null)
    {
        MemberExpression mi = exp as MemberExpression;

        if (mi != null)
        {
            members.Add(mi.Member);
            exp = mi.Expression;
        }
        else
        {
            ce = exp as ConstantExpression;

            if (ce == null)
            {
                // We support only a ConstantExpression at the base
                // no function call like
                // () => myfunc().A.B.C
                throw new NotSupportedException();
            }

            break;
        }
    }

    if (members.Count == 0)
    {
        // We need at least a getter
        throw new NotSupportedException();
    }

    // Now we must walk the getters (excluding the last).
    // From the ConstantValue ce we take the base object
    object targetObject = ce.Value;

    // We have to walk the getters from last (most inner) to second
    // (the first one is the one we have to use as a setter)
    for (int i = members.Count - 1; i >= 1; i--)
    {
        PropertyInfo pi = members[i] as PropertyInfo;

        if (pi != null)
        {
            targetObject = pi.GetValue(targetObject);
        }
        else
        {
            FieldInfo fi = (FieldInfo)members[i];
            targetObject = fi.GetValue(targetObject);
        }
    }

    // The first one is the getter we treat as a setter
    {
        PropertyInfo pi = members[0] as PropertyInfo;

        if (pi != null)
        {
            pi.SetValue(targetObject, valueToSet);
        }
        else
        {
            FieldInfo fi = (FieldInfo)members[0];
            fi.SetValue(targetObject, valueToSet);
        }
    }
}

You use it like this:

A a = new A();

SetDeepValue(a, () => a.B.C.Value, "Foo");

Note that the SetDeepValue doesn't need, nor uses, the targetObject, because it can discover it in the chain of getters:

SetDeepValue(myCustomer, ()=>myCustomer.FirstName, currentTestCaseValue);

Here you have ()=>myCustomer.

It would have been necessary if you call was in the form

SetDeepValue(myCustomer, x=>x.FirstName, currentTestCaseValue);

I'll even give you a method that uses this second format of Expression:

public static void SetDeepValue<TObject, T>(TObject target, Expression<Func<TObject, T>> propertyToSet, T valueToSet)
{
    List<MemberInfo> members = new List<MemberInfo>();

    Expression exp = propertyToSet.Body;

    // There is a chain of getters in propertyToSet, with at the 
    // beginning a ParameterExpression. We put the MemberInfo of
    // these getters in members. We don't really need the 
    // ParameterExpression

    while (exp != null)
    {
        MemberExpression mi = exp as MemberExpression;

        if (mi != null)
        {
            members.Add(mi.Member);
            exp = mi.Expression;
        }
        else
        {
            ParameterExpression pe = exp as ParameterExpression;

            if (pe == null)
            {
                // We support only a ParameterExpression at the base
                throw new NotSupportedException();
            }

            break;
        }
    }

    if (members.Count == 0)
    {
        // We need at least a getter
        throw new NotSupportedException();
    }

    // Now we must walk the getters (excluding the last).
    object targetObject = target;

    // We have to walk the getters from last (most inner) to second
    // (the first one is the one we have to use as a setter)
    for (int i = members.Count - 1; i >= 1; i--)
    {
        PropertyInfo pi = members[i] as PropertyInfo;

        if (pi != null)
        {
            targetObject = pi.GetValue(targetObject);
        }
        else
        {
            FieldInfo fi = (FieldInfo)members[i];
            targetObject = fi.GetValue(targetObject);
        }
    }

    // The first one is the getter we treat as a setter
    {
        PropertyInfo pi = members[0] as PropertyInfo;

        if (pi != null)
        {
            pi.SetValue(targetObject, valueToSet);
        }
        else
        {
            FieldInfo fi = (FieldInfo)members[0];
            fi.SetValue(targetObject, valueToSet);
        }
    }
}

You can compare the two to see the differences.



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