使用FieldInfo.SetValue与LINQ表达式在结构中设置字段

我想使用LINQ表达式设置私有字段。 我有这个代码:

//parameter "target", the object on which to set the field `field` ParameterExpression targetExp = Expression.Parameter(typeof(object), "target"); //parameter "value" the value to be set in the `field` on "target" ParameterExpression valueExp = Expression.Parameter(typeof(object), "value"); //cast the target from object to its correct type Expression castTartgetExp = Expression.Convert(targetExp, type); //cast the value to its correct type Expression castValueExp = Expression.Convert(valueExp, field.FieldType); //the field `field` on "target" MemberExpression fieldExp = Expression.Field(castTartgetExp, field); //assign the "value" to the `field` BinaryExpression assignExp = Expression.Assign(fieldExp, castValueExp); //compile the whole thing var setter = Expression.Lambda<Action> (assignExp, targetExp, valueExp).Compile(); 

这将编译一个带有两个对象的委托,即目标和值:

 setter(someObject, someValue); 

type变量指定目标的Type ,而field变量是FieldInfo ,它指定要设置的字段。

这适用于引用类型,但如果目标是结构,那么这个东西会将目标作为副本传递给setter委托并在副本上设置值,而不是像我想要的那样在原始目标上设置值。 (至少这是我的想法。)

另一方面,

 field.SetValue(someObject, someValue); 

工作得很好,即使是结构。

为了使用编译的表达式设置目标字段,我能做些什么吗?

对于值类型,请使用Expression.Unbox而不是Expression.Convert 。

 //cast the target from object to its correct type Expression castTartgetExp = type.IsValueType ? Expression.Unbox(targetExp, type) : Expression.Convert(targetExp, type); 

这是一个演示: .NET小提琴


问: setter方法没有ref参数。 它如何更新原始结构?

答:虽然没有ref关键字,值类型通常按值传递并因此被复制,但这里target参数的类型是object 。 如果参数是盒装结构,则对该框的引用 (按值)传递给该方法。

现在,使用纯C#来改变盒装结构是不可能的,因为C#取消装箱转换总是产生盒装值的副本。 但是可能使用IL或Reflection:

 public struct S { public int I; } public void M(object o, int i) { // ((S)o).I = i; // DOESN'T COMPILE typeof(S).GetField("I").SetValue(o, i); } public void N() { S s = new S(); object o = s; // create a boxed copy of s M(o, 1); // mutate o (but not s) Console.WriteLine(((S)o).I); // "1" Console.WriteLine(sI); // "0" M(s, 2); // mutate a TEMPORARY boxed copy of s (BEWARE!) Console.WriteLine(sI); // "0" } 

问:如果LINQ表达式使用Expression.Convert,为什么setter不工作?

答: Expression.Convert编译为unbox.any IL指令,它返回target引用的struct的副本 。 然后,setter更新此副本(随后将其丢弃)。

问:为什么Expression.Unbox会解决问题?

答: Expression.Unbox(当用作Expression.Assign的目标时)编译为unbox IL指令,该指令返回指向 target引用的结构的指针 。 然后,setter使用指针直接修改该结构。