问题
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