创建一个将从对象返回属性值的lambda函数相对容易,甚至包括深层属性......
Func<Category, string> getCategoryName = new Func<Category, string>(c => c.Name);
这可以如下调用......
string categoryName = getCategoryName(this.category);
但是,鉴于 仅 上面生成的函数(或最初用于创建函数的表达式),任何人都可以提供一种简单的方法来创建相反的操作。
Action<Category, string> setCategoryName = new Action<Category, string>((c, s) => 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,“Expression必须是可写的”。
再次感谢。
答案 0 :(得分:5)
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 :(得分:3)
好的,我正在寻找的代码是这样的......
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函数可用于深层属性。知道是否有人可以帮助我修改它以使用深层属性会很有趣,但我会将其作为一个单独的问题提出来。
答案 2 :(得分:2)
正如马丁所说,“深层”属性打破了他的解决方案。它不起作用的原因是这个表达式:
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
)
);
}
答案 3 :(得分:0)
这应该可以使用expression trees,可以从lambda表达式创建,修改,然后编译成委托。