如何在 C# 表达式树中设置字段值?
Posted
技术标签:
【中文标题】如何在 C# 表达式树中设置字段值?【英文标题】:How do I set a field value in an C# Expression tree? 【发布时间】:2010-09-24 05:07:03 【问题描述】:给定:
FieldInfo field = <some valid string field on type T>;
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
如何编译 lambda 表达式以将“target”参数上的字段设置为“value”?
【问题讨论】:
【参考方案1】:.Net 4.0:现在有了Expression.Assign
,这很容易做到:
FieldInfo field = typeof(T).GetField("fieldName");
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
// Expression.Property can be used here as well
MemberExpression fieldExp = Expression.Field(targetExp, field);
BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp);
var setter = Expression.Lambda<Action<T, string>>
(assignExp, targetExp, valueExp).Compile();
setter(subject, "new value");
.Net 3.5:你不能,你必须改用 System.Reflection.Emit:
class Program
class MyObject
public int MyField;
static Action<T,TValue> MakeSetter<T,TValue>(FieldInfo field)
DynamicMethod m = new DynamicMethod(
"setter", typeof(void), new Type[] typeof(T), typeof(TValue) , typeof(Program));
ILGenerator cg = m.GetILGenerator();
// arg0.<field> = arg1
cg.Emit(OpCodes.Ldarg_0);
cg.Emit(OpCodes.Ldarg_1);
cg.Emit(OpCodes.Stfld, field);
cg.Emit(OpCodes.Ret);
return (Action<T,TValue>) m.CreateDelegate(typeof(Action<T,TValue>));
static void Main()
FieldInfo f = typeof(MyObject).GetField("MyField");
Action<MyObject,int> setter = MakeSetter<MyObject,int>(f);
var obj = new MyObject();
obj.MyField = 10;
setter(obj, 42);
Console.WriteLine(obj.MyField);
Console.ReadLine();
【讨论】:
很好的回应巴里,你回答了我最初的问题。我将发布另一个问题,我需要先调用转换的操作码......谢谢! 只是好奇,这种方法与仅使用 System.Reflection 和 MemberInfos 设置属性有什么区别? 我对您的回答 +1 感到非常惊讶。干得好!恭喜:) 非常有用的东西,但要小心极端情况!此 MakeSetter 方法不处理值类型的情况。 @Oleg Mihailik:你说不处理值类型是什么意思?如果该字段是值类型,则没有问题。如果源 (T) 是值类型,我认为没有任何 setter 可以真正按预期工作。【参考方案2】:如前所述,设置字段是有问题的。您可以(在 3.5 中)使用单个方法,例如属性设置器 - 但只能是间接的。正如 here 所讨论的,这在 4.0 中变得更加容易。但是,如果您确实有属性(而不是字段),您可以简单地使用 Delegate.CreateDelegate
做很多事情:
using System;
using System.Reflection;
public class Foo
public int Bar get; set;
static class Program
static void Main()
MethodInfo method = typeof(Foo).GetProperty("Bar").GetSetMethod();
Action<Foo, int> setter = (Action<Foo, int>)
Delegate.CreateDelegate(typeof(Action<Foo, int>), method);
Foo foo = new Foo();
setter(foo, 12);
Console.WriteLine(foo.Bar);
【讨论】:
我很想听听为什么它被否决了……对我来说似乎是一个相当不错的旁观者;仅适用于属性,但不需要 Reflection.Emit 或 Expression... 在给定的链接中,提到了Expression.AssignProperty
。那是什么?
@nawful 在 4.0 树中更加丰富,允许完整的代码块。然而,大多数 linq 提供程序和编译器都不允许您这样做。我假设它是通过属性分配的;)
@MarcGravell 你能否正确地标记我的名字,以便我收到警报。哦,好的,但是你从哪里找到这个 Expression.AssignProperty
的?一些文档或链接?
@nawfal 抱歉打错了;如果您阅读该帖子,它会说它基于 4.0 CTP - 即 .NET 4.0 的预发布版本;大概这在 RTM 版本中更改为 .Assign。【参考方案3】:
private static Action<object, object> CreateSetAccessor(FieldInfo field)
DynamicMethod setMethod = new DynamicMethod(field.Name, typeof(void), new[] typeof(object), typeof(object) );
ILGenerator generator = setMethod.GetILGenerator();
LocalBuilder local = generator.DeclareLocal(field.DeclaringType);
generator.Emit(OpCodes.Ldarg_0);
if (field.DeclaringType.IsValueType)
generator.Emit(OpCodes.Unbox_Any, field.DeclaringType);
generator.Emit(OpCodes.Stloc_0, local);
generator.Emit(OpCodes.Ldloca_S, local);
else
generator.Emit(OpCodes.Castclass, field.DeclaringType);
generator.Emit(OpCodes.Stloc_0, local);
generator.Emit(OpCodes.Ldloc_0, local);
generator.Emit(OpCodes.Ldarg_1);
if (field.FieldType.IsValueType)
generator.Emit(OpCodes.Unbox_Any, field.FieldType);
else
generator.Emit(OpCodes.Castclass, field.FieldType);
generator.Emit(OpCodes.Stfld, field);
generator.Emit(OpCodes.Ret);
return (Action<object, object>)setMethod.CreateDelegate(typeof(Action<object, object>));
【讨论】:
看来FieldInfo.DeclaringType
必须是公开的才能使其正常工作。否则,它会在 .NET 3.5 中出现 TypeLoadException
和在 .NET 4.0/4.5 中出现 TypeAccessException
失败(尽管 Expression.Assign
在 4.0+ 中可用,所以对于 3.5 来说问题更大)。
啊,没关系。只需在DynamicMethod
构造函数中设置restrictedSkipVisibility
。【参考方案4】:
实际上,在 .NET 3.5 中有一种方法可以使用表达式树设置属性和字段。它可能是一些不支持Delegate.CreateDelegate
的 PCL 配置文件的唯一选择(除了 Reflection.Emit):
对于字段,诀窍是将字段作为 ref 参数传递,
例如SetField(ref holder.Field, "NewValue");
属性(正如 Marc 已经指出的那样)可以通过反射和调用它的 setter 方法来设置。
下面提供了完整的概念证明作为 NUnit 测试夹具。
[TestFixture]
public class CanSetPropAndFieldWithExpressionTreeInNet35
class Holder
public int Field;
public string Prop get; set;
public static class FieldAndPropSetter
public static T SetField<T, TField>(T holder, ref TField field, TField value)
field = value;
return holder;
public static T SetProp<T>(T holder, Action<T> setProp)
setProp(holder);
return holder;
[Test]
public void Can_set_field_with_expression_tree_in_Net35()
// Shows how expression could look like:
Func<Holder, Holder> setHolderField = h => FieldAndPropSetter.SetField(h, ref h.Field, 111);
var holder = new Holder();
holder = setHolderField(holder);
Assert.AreEqual(111, holder.Field);
var holderType = typeof(Holder);
var field = holderType.GetField("Field");
var fieldSetterMethod =
typeof(FieldAndPropSetter).GetMethod("SetField")
.MakeGenericMethod(holderType, field.FieldType);
var holderParamExpr = Expression.Parameter(holderType, "h");
var fieldAccessExpr = Expression.Field(holderParamExpr, field);
// Result expression looks like: h => FieldAndPropSetter.SetField(h, ref h.Field, 222)
var setHolderFieldExpr = Expression.Lambda<Func<Holder, Holder>>(
Expression.Call(fieldSetterMethod, holderParamExpr, fieldAccessExpr, Expression.Constant(222)),
holderParamExpr);
var setHolderFieldGenerated = setHolderFieldExpr.Compile();
holder = setHolderFieldGenerated(holder);
Assert.AreEqual(222, holder.Field);
[Test]
public void Can_set_property_with_expression_tree_in_Net35()
// Shows how expression could look like:
Func<Holder, Holder> setHolderProp = h => FieldAndPropSetter.SetProp(h, _ => _.Prop = "ABC");
var holder = new Holder();
holder = setHolderProp(holder);
Assert.AreEqual("ABC", holder.Prop);
var holderType = typeof(Holder);
var prop = holderType.GetProperty("Prop");
var propSet = prop.GetSetMethod();
var holderParamExpr = Expression.Parameter(holderType, "h");
var callSetPropExpr = Expression.Call(holderParamExpr, propSet, Expression.Constant("XXX"));
var setPropActionExpr = Expression.Lambda(callSetPropExpr, holderParamExpr);
var propSetterMethod = typeof(FieldAndPropSetter).GetMethod("SetProp").MakeGenericMethod(holderType);
// Result expression looks like: h => FieldAndPropSetter.SetProp(h, _ => _.Prop = "XXX")
var setHolderPropExpr = Expression.Lambda<Func<Holder, Holder>>(
Expression.Call(propSetterMethod, holderParamExpr, setPropActionExpr),
holderParamExpr);
var setHolderPropGenerated = setHolderPropExpr.Compile();
holder = setHolderPropGenerated(holder);
Assert.AreEqual("XXX", holder.Prop);
【讨论】:
【参考方案5】:我曾经做过这门课。也许有帮助:
public class GetterSetter<EntityType,propType>
private readonly Func<EntityType, propType> getter;
private readonly Action<EntityType, propType> setter;
private readonly string propertyName;
private readonly Expression<Func<EntityType, propType>> propertyNameExpression;
public EntityType Entity get; set;
public GetterSetter(EntityType entity, Expression<Func<EntityType, propType>> property_NameExpression)
Entity = entity;
propertyName = GetPropertyName(property_NameExpression);
propertyNameExpression = property_NameExpression;
//Create Getter
getter = propertyNameExpression.Compile();
// Create Setter()
MethodInfo method = typeof (EntityType).GetProperty(propertyName).GetSetMethod();
setter = (Action<EntityType, propType>)
Delegate.CreateDelegate(typeof(Action<EntityType, propType>), method);
public propType Value
get
return getter(Entity);
set
setter(Entity, value);
protected string GetPropertyName(LambdaExpression _propertyNameExpression)
var lambda = _propertyNameExpression as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
else
memberExpression = lambda.Body as MemberExpression;
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.Name;
测试:
var gs = new GetterSetter<OnOffElement,bool>(new OnOffElement(), item => item.IsOn);
gs.Value = true;
var result = gs.Value;
【讨论】:
不回答问题。关于FieldInfo
【参考方案6】:
为了完整起见,这里是 getter:
public static IEnumerable<Func<T, object>> GetTypeGetters<T>()
var fields = typeof (T).GetFields();
foreach (var field in fields)
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
UnaryExpression boxedFieldExp = Expression.Convert(Expression.Field(targetExp, field), typeof(object));
yield return Expression.Lambda<Func<T,object>>(boxedFieldExp, targetExp).Compile();
【讨论】:
以上是关于如何在 C# 表达式树中设置字段值?的主要内容,如果未能解决你的问题,请参考以下文章