10

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?

GWLlosa
  • 23,995
  • 17
  • 79
  • 116
  • 1
    Give us some more lines of program, and an example of propertyToValidate/objectUnderTest/excessivelyLargeNameValue. – xanatos Mar 16 '15 at 19:04
  • I'm pretty sure the for the nested objects, you need to actually get the object, and then perform another `.SetValue()` call on that object – JNYRanger Mar 16 '15 at 19:05
  • See if http://stackoverflow.com/a/7723923/613130 is what you need... From a getter it returns an Action setter – xanatos Mar 16 '15 at 19:09
  • I'd say http://stackoverflow.com/questions/16208214/construct-lambdaexpression-for-nested-property-from-string is what OP is looking for. – abatishchev Mar 16 '15 at 19:13
  • It isn't clear if the OP wants to parse a string to create a delegate (so given `x` and `"A.B.C.D" ` set the value of `x.A.B.C.D` or wants to change a `x => x.A.B.C.D` from getter to setter and set it to a value – xanatos Mar 16 '15 at 19:14
  • 1
    I added some examples that hopefully clear things up a bit more. – GWLlosa Mar 16 '15 at 19:27

2 Answers2

8

Here's how you would usually convert string "ColumnName1.ColumnName2" to a lambda expression x => x.ColumnName1.ColumnName2:

Expression<Func<T, object>> ForNestedProperty(string columnName)
{
    // x
    ParameterExpression param = Expression.Parameter(typeof(T), "x");

    // x.ColumnName1.ColumnName2
    Expression property = columnName.Split('.')
                                    .Aggregate<string, Expression>
                                    (param, (c, m) => Expression.Property(c, m));

    // x => x.ColumnName1.ColumnName2
    Expression<Func<T, object>> lambda = Expression.Lambda<Func<T, object>>(
        Expression.Convert(property, typeof(object)), param);
    return lambda;
}

(Copied from here)

abatishchev
  • 98,240
  • 88
  • 296
  • 433
  • 2
    I was going to say, "You should give credit to the original author", then I looked a little more carefully... – vcsjones Mar 16 '15 at 19:18
  • 1
    I want to kiss you sir. Such a neat solution. Just in one line brilliant sir – Ali Jan 31 '19 at 05:10
4

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.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • This makes much more sense. I'm going to try to get it all in my head and understood and then try it today. Are there any particular books/blogs/web resources that really cover Expressions in detail that you'd recommend, by the way? – GWLlosa Mar 17 '15 at 13:36
  • @GWLlosa I don't know... I studied them some years ago and from then I didn't need any "basic" resource... And I don't remember how I studied them. But note that I didn't do anything funny with the expression here. I simply walked it backward. If you have specific questions on Expression trees, here on SO there are many Expression-trees nuts :-) – xanatos Mar 17 '15 at 13:43