【问题标题】:How to use an Expression<Func> to set a nested property?如何使用 Expression<Func> 设置嵌套属性?
【发布时间】:2015-03-16 19:01:18
【问题描述】:

所以我有一些代码可以设置对象的属性。此代码来自我们在单元测试中使用的内部验证类。所以代码可能会提供类似

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

此代码用于单元测试类型的环境,然后我们可以在其中进行类似的调用

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

(为了简洁/复杂而简化的代码)

然而,现在,由于一些重构,我们得到了类似的东西:

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

当这段代码运行时,我们得到错误:

对象与目标类型不匹配。

我怀疑它试图调用BusinessObject 上的First 属性,而不是NameInfo。如何修改我的代码来处理这种“嵌套”情况?

【问题讨论】:

  • 再给我们几行程序,以及propertyToValidate/objectUnderTest/excessivelyLargeNameValue的例子。
  • 我很确定对于嵌套对象,您需要实际获取该对象,然后对该对象执行另一个 .SetValue() 调用
  • 看看stackoverflow.com/a/7723923/613130是不是你需要的......从一个getter它返回一个Action setter
  • 我会说stackoverflow.com/questions/16208214/… 是 OP 正在寻找的。​​span>
  • 我添加了一些示例,希望能更清楚地说明问题。

标签: c# .net reflection expression-trees


【解决方案1】:

以下是您通常如何将字符串 "ColumnName1.ColumnName2" 转换为 lambda 表达式 x =&gt; 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;
}

(复制自here

【讨论】:

  • 我正要说“你应该感谢原作者”,然后我仔细看了看......
  • 我想吻你先生。如此巧妙的解决方案。就在一行辉煌先生
【解决方案2】:

现在您已经给我们举了一个例子,这很容易做到。以任何方式编译表达式都是没有用的,因为我们不能重用它,所以它只会减慢方法。更容易走 getter 的“链”并使用反射来访问它们的值。我写的方法同时支持字段(通常用作readonly字段)和属性。

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);
        }
    }
}

你可以这样使用它:

A a = new A();

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

请注意,SetDeepValue 不需要也不会使用 targetObject,因为它可以在 getter 链中发现它:

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

这里有()=&gt;myCustomer

如果你调用的格式是这样的,那将是必要的

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

我什至会给你一个使用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);
        }
    }
}

您可以比较两者以查看差异。

【讨论】:

  • 这更有意义。我将尝试将这一切都放在我的脑海中并理解,然后今天尝试一下。顺便说一句,是否有任何特定的书籍/博客/网络资源真正详细地介绍了您推荐的表达式?
  • @GWLlosa 我不知道...几年前我研究过它们,从那时起我不需要任何“基本”资源...而且我不记得我是如何研究它们的.但请注意,我没有对这里的表达式做任何有趣的事情。我只是向后走。如果您对表达式树有具体问题,那么这里有很多表达式树坚果 :-)
猜你喜欢
  • 2011-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多