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

泪湿孤枕 提交于 2020-01-11 05:40:08

问题


I want to set private fields using LINQ expressions. I have this code:

//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();

This compiles a delegate that takes two objects, the target and the value:

setter(someObject, someValue);

The type variable specifies the Type of the target, and the field variable is a FieldInfo that specifies the field to be set.

This works great for reference types, but if the target is a struct, then this thing will pass the target as a copy to the setter delegate and set the value on the copy, instead of setting the value on the original target like I want. (At least that is what I think is going on.)

On the other hand,

field.SetValue(someObject, someValue);

works just fine, even for structs.

Is there anything I can do about this in order to set the field of the target using the compiled expression?


回答1:


For value types, use 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);

Here's a demo: .NET Fiddle


Q: The setter method doesn't have a ref parameter. How can it update the original struct?

A: Although it's true that, without the ref keyword, value types are normally passed by value and thus copied, here the type of the target parameter is object. If the argument is a boxed struct, then a reference to the box is passed (by value) to the method.

Now, it's not possible using pure C# to mutate a boxed struct because a C# unboxing conversion always produces a copy of the boxed value. But it is possible using IL or 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"
}

Q: Why doesn't the setter work if the LINQ expression uses Expression.Convert?

A: Expression.Convert compiles to the unbox.any IL instruction, which returns a copy of the struct referenced by target. The setter then updates this copy (which is subsequently discarded).

Q: Why does Expression.Unbox fix the problem?

A: Expression.Unbox (when used as the target of Expression.Assign) compiles to the unbox IL instruction, which returns a pointer to the struct referenced by target. The setter then uses the pointer to modify that struct directly.



来源:https://stackoverflow.com/questions/32158399/using-fieldinfo-setvalue-vs-linq-expressions-to-set-a-field-in-a-struct

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!