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

Using FieldInfo.SetValue vs LINQ expressions to set a field in a struct

我想使用 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<object, object>> (assignExp, targetExp, valueExp).Compile();

这编译了一个接受两个对象的委托,目标和值:

setter(someObject, someValue);

type变量指定目标的Typefield变量是一个FieldInfo,指定要设置的字段

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

另一方面,

field.SetValue(someObject, someValue);

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

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

对于值类型,使用 Expression.Unbox instead of Expression.Convert

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

这是一个演示:.NET Fiddle


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

A: 虽然确实没有 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(s.I);      // "0"

    M(s, 2); // mutate a TEMPORARY boxed copy of s (BEWARE!)
    Console.WriteLine(s.I);      // "0"
}

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

A: Expression.Convert 编译成 unbox.any IL 指令,returns a copy target 引用的结构。 setter 然后更新此副本(随后被丢弃)。

问:为什么Expression.Unbox解决了这个问题?

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