使用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<object, object>> (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 Fiddle


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

A:虽然在没有ref关键字的情况下,值类型通常是按值传递并复制的,但这里target参数的类型是object。如果实参是一个盒装结构体,则将对该框的引用(按值)传递给方法。

现在,不可能使用纯c#来改变盒装结构体,因为c#拆箱转换总是产生盒装值的副本。但是它可能使用IL或反射:

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"
}

Q:如果LINQ表达式使用expression . convert,为什么setter不能工作?

: 表达式。将编译器转换为unbox.any IL指令,该指令返回target引用的结构体的副本。然后setter更新该副本(随后丢弃)。

Q:为什么表达式。开箱解决问题?

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

相关内容

  • 没有找到相关文章

最新更新