【问题标题】:Create lambda action from function expression从函数表达式创建 lambda 动作
【发布时间】:2010-05-15 16:02:23
【问题描述】:

创建一个返回对象属性值的 lambda 函数相对容易,甚至包括深层属性...

Func<Category, string> getCategoryName = new Func<Category, string>(c => c.Name);

这可以如下调用...

string categoryName = getCategoryName(this.category);

但是,鉴于上面的结果函数(或最初用于创建函数的表达式),任何人都可以提供一种简单的方法来创建相反的动作.. .

Action&lt;Category, string&gt; setCategoryName = new Action&lt;Category, string&gt;((c, s) =&gt; c.Name = s);

...这将使相同的属性值设置如下?

setCategoryName(this.category, "");

请注意,我正在寻找一种从函数或表达式以编程方式创建动作的方法 - 我希望我已经表明我已经知道如何手动创建它。

我愿意接受适用于 .net 3.5 和 4.0 的答案。

谢谢。

更新:

也许我的问题并不清楚,所以让我试着更清楚地展示我想要做什么。

我有以下方法(我为此问题创建的)...

void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) {

    Func<TObject, TValue> getValue = expression.Compile();
    TValue stuff = getValue(obj);

    Expression<Action<TObject, TValue>> assignmentExpression = (o, v) => Expression<TObject>.Assign(expression, Expression.Constant(v, typeof(TValue)));
    Action<TObject, TValue> setValue = assignmentExpression.Compile();

    setValue(obj, stuff);

}

我正在寻找的是如何在代码中创建“assignmentExpression”,以便可以将其编译为 setValue?我认为它与 Expression.Assign 相关,但我根本无法计算出正确的参数组合来完成代码。

最终的结果是能够调用

Category category = *<get object from somewhere>*;
this.DoLambdaStuff(category, c => c.Name);

这又会为 Category 对象的“Name”属性创建一个 getter 和一个 setter。

上面的版本可以编译,但是当我调用 setValue() 时,它会导致 ArgumentException 出现“表达式必须是可写的”。

再次感谢。

【问题讨论】:

  • 我真的不明白你的意思是自动而不是手动。但是,如果要设置属性并决定要在运行时设置哪个属性,则应使用反射。此外,您可以使用表达式树来构建运行时 lambda 表达式。

标签: .net lambda


【解决方案1】:
void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) {

    Func<TObject, TValue> getValue = expression.Compile();
    TValue stuff = getValue(obj);

    var p = Expression.Parameter(typeof(TValue), "v");
    Expression<Action<TObject, TValue>> assignmentExpression = 
        Expression.Lambda<Action<TObject, TValue>>(Expression.Assign(expression.Body, p), expression.Parameters[0], p);

    Action<TObject, TValue> setValue = assignmentExpression.Compile();

    setValue(obj, stuff);
}

【讨论】:

  • +1。使用此代码,我还可以设置 private fieldsnested private properties 的值。字段/属性的类型也可以是struct,它可以正常工作。真的很想解释一下这是如何工作的。
【解决方案2】:

好的,我正在寻找的代码是这样的......

ParameterExpression objectParameterExpression = Expression.Parameter(
  typeof(TObject)),
  valueParameterExpression = Expression.Parameter(typeof(TValue)
);
Expression<Action<TObject, TValue>> setValueExpression = Expression.Lambda<Action<TObject, TValue>>(
  Expression.Block(
    Expression.Assign(
      Expression.Property(
        objectParameterExpression,
        ((MemberExpression) expression.Body).Member.Name
      ),
      valueParameterExpression
    )
  ),
  objectParameterExpression,
  valueParameterExpression
);
Action<TObject, TValue> setValue = setValueExpression.Compile();

此代码有效,但仅适用于浅层属性(即直接对象的属性),但不适用于深层属性 - 尽管 getter 函数可以适用于深层属性。知道是否有人可以帮助我修改它以使用深层属性会很有趣,但我会将此作为一个单独的问题提出。

【讨论】:

  • 此代码仅适用于 .NET 4,因为它需要改进对 .NET 4 中引入的表达式树的支持。
【解决方案3】:

如上所述,“深层”属性破坏了他的解决方案。不起作用的原因是这个表达式:

Expression.Property(
    objectParameterExpression
,  ((MemberExpression)expression.Body).Member.Name
)

原因还不是很明显:派生类声明了自己的属性getter,它重定向到基类,但没有定义对应的setter。

为了解决这个问题,需要通过反射定位属性,查找其声明类型,然后从声明者处获取对应的属性。与您在派生类上获得的属性不同,来自声明器的属性有一个 getter 和一个 setter,因此是可赋值的。 (这假设完全声明了属性的 setter:如果声明者不提供 setter,那么其他人不可能提供它。)

这是一个解决这个问题的框架实现。将上面对Expression.Property的调用替换为下面对GetPropertyOrField的调用,Martin的解决方案就可以了。

private static MemberExpression GetPropertyOrField(Expression baseExpr, string name) {
    if (baseExpr == null) {
        throw new ArgumentNullException("baseExpr");
    }
    if (string.IsNullOrWhiteSpace(name)) {
        throw new ArgumentException("name");
    }
    var type = baseExpr.Type;
    var properties = type
        .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
        .Where(p => p.Name.Equals(name))
        .ToArray();
    if (properties.Length == 1) {
        var res = properties[0];
        if (res.DeclaringType != type) {
            // Here is the core of the fix:
            var tmp = res
                .DeclaringType
                .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(p => p.Name.Equals(name))
                .ToArray();
            if (tmp.Length == 1) {
                return Expression.Property(baseExpr, tmp[0]);
            }
        }
        return Expression.Property(baseExpr, res);
    }
    if (properties.Length != 0) {
        throw new NotSupportedException(name);
    }
    var fields = type
        .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
        .Where(p => p.Name.Equals(name))
        .ToArray();
    if (fields.Length == 1) {
        return Expression.Field(baseExpr, fields[0]);
    }
    if (fields.Length != 0) {
        throw new NotSupportedException(name);
    }
    throw new ArgumentException(
        string.Format(
            "Type [{0}] does not define property/field called [{1}]"
        ,   type
        ,   name
        )
    );
}

【讨论】:

    【解决方案4】:

    这应该可以使用expression trees 来实现,它可以从 lambda 表达式创建、修改,然后编译成一个委托。

    【讨论】:

    • 这是我一直在寻找的东西,我只是希望有一个例子!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-13
    • 2015-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多