How can I set value into struct field - myStruct.myField
with reflection using DynamicMethod? When I call setter(myStruct, 111)
value was not set, because MyStruct
is value type. Console.WriteLine(myStruct.myField)
shows value 3.
How to modify GetDelegate
method to set value into myStruct.myField
?
public struct MyStruct
{
public int myField;
}
public delegate void SetHandler(object source, object value);
private static SetHandler GetDelegate(Type type, FieldInfo fieldInfo)
{
DynamicMethod dm = new DynamicMethod("setter", typeof(void), new Type[] { typeof(object), typeof(object) }, type, true);
ILGenerator setGenerator = dm.GetILGenerator();
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.DeclareLocal(type);
setGenerator.Emit(OpCodes.Unbox_Any, type);
setGenerator.Emit(OpCodes.Stloc_0);
setGenerator.Emit(OpCodes.Ldloca_S, 0);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType);
setGenerator.Emit(OpCodes.Stfld, fieldInfo);
setGenerator.Emit(OpCodes.Ldloc, 0);
setGenerator.Emit(OpCodes.Box, type);
setGenerator.Emit(OpCodes.Ret);
return (SetHandler)dm.CreateDelegate(typeof(SetHandler));
}
MyStruct myStruct = new MyStruct();
myStruct.myField = 3;
FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance);
SetHandler setter = GetDelegate(typeof(MyStruct), fi);
setter(myStruct, 111);
Console.WriteLine(myStruct.myField);
Edit: I made this mistake again - fun fact; unbox-any returns the value; unbox returns the pointer to the data - which allows in-place mutate.
Here's the working IL generation:
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Unbox, type);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType);
setGenerator.Emit(OpCodes.Stfld, fieldInfo);
setGenerator.Emit(OpCodes.Ret);
But! This is mutating a boxed copy; you would need to unbox afterwards:
object obj = myStruct;
setter(obj, 111);
MyStruct andBackAgain = (MyStruct)obj;
Console.WriteLine(andBackAgain.myField);
Console.WriteLine(myStruct.myField);
To do it in place, you would probably need the method to take a ref MyStruct
, or to return a MyStruct
. You could return the boxed copy, but that doesn't make it much easier to use. Frankly, it is moot: structs should not generally be mutable.
I think as Servy points out in the comments it's probably better to not have a mutable struct and instead create a copy of the struct with the new value. You can use the SetValueDirect
method on the FieldInfo
instance if you really want to use reflection to mutate the struct but it uses the undocumented __makeref
method to get a TypedReference
.
I really wouldn't recommend using this code; it's purely to show that this is possible:
MyStruct myStruct = new MyStruct();
myStruct.myField = 3;
FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance);
TypedReference reference = __makeref(myStruct);
fi.SetValueDirect(reference, 111);
Console.WriteLine(myStruct.myField); //prints 111
The easiest way to use Reflection to set fields or properties of a structure is to box the structure, pass the boxed structure to SetField
or SetProperty
, and then unbox the structure once all desired manipulations are complete.
public static class refStructTest
{
struct S1
{
public int x;
public int y { get; set; }
public override string ToString()
{
return String.Format("[{0},{1}]", x, y);
}
}
public static void test()
{
var s = default(S1);
s.x = 2;
s.y = 3;
Object obj = s;
var fld = typeof(S1).GetField("x");
var prop = typeof(S1).GetProperty("y");
fld.SetValue(obj,5);
prop.SetValue(obj,6,null);
s = (S1)obj;
Console.WriteLine("Result={0}", s);
}
}
According to the ECMA documentation, each value type is associated with two kinds of things: a storage location type and a heap object type. The heap object type, like all heap object types, will behave with reference semantics; passing a reference to a heap object to a method like SetValue
will thus modify the object to which the reference was passed.
Note for VB users: VB.NET has a really annoying behavior which almost make sense in the Option Strict On
dialect, but which exists even in the Option Strict Off
dialect: if a variable of compile-time type Object
which holds a reference to a boxed structure is assigned to another variable of that same type or passed as a parameter of type Object
, VB.NET will store or pass a reference to a copy of the original object. When writing code like the above in VB.NET, one should make obj
be of type ValueType
rather than Object
to prevent such behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With