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

Rog*_*son 4 .net c# reflection linq-expressions

我想使用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();
Run Code Online (Sandbox Code Playgroud)

这将编译一个接受两个对象(目标和值)的委托:

setter(someObject, someValue);
Run Code Online (Sandbox Code Playgroud)

type变量指定Type的目标,并且所述field变量是一个FieldInfo指定字段设置。

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

另一方面,

field.SetValue(someObject, someValue);
Run Code Online (Sandbox Code Playgroud)

即使对于结构也可以正常工作。

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

Mic*_*Liu 5

对于值类型,请使用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);
Run Code Online (Sandbox Code Playgroud)

这是一个演示:.NET Fiddle


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

答:虽然的确是这样,但是如果没有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"
}
Run Code Online (Sandbox Code Playgroud)

问:如果LINQ表达式使用Expression.Convert,设置器为何不起作用?

答: Expression.Convert编译为unbox.anyIL指令,该指令返回所引用结构的副本target。然后,设置员将更新此副本(随后将其丢弃)。

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

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