[英]C# Reflection - How to set field value for struct
How can I set value into struct field - myStruct.myField
with reflection using DynamicMethod?如何使用 DynamicMethod 将值设置到结构字段 -
myStruct.myField
? When I call setter(myStruct, 111)
value was not set, because MyStruct
is value type.当我调用
setter(myStruct, 111)
时没有设置值,因为MyStruct
是值类型。 Console.WriteLine(myStruct.myField)
shows value 3. Console.WriteLine(myStruct.myField)
显示值 3。
How to modify GetDelegate
method to set value into myStruct.myField
?如何修改
GetDelegate
方法以将值设置为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-any 返回值; unbox returns the pointer to the data - which allows in-place mutate.
unbox 返回指向数据的指针- 这允许就地变异。
Here's the working IL generation:这是工作的 IL 生成:
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
.要做到这一点,您可能需要使用
ref MyStruct
或返回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.我认为正如 Servy 在评论中指出的那样,最好没有可变结构,而是使用新值创建结构的副本。 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
.如果你真的想使用反射来改变结构,你可以在
FieldInfo
实例上使用SetValueDirect
方法,但它使用未记录的__makeref
方法来获取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
Here the code with ref:这里有 ref 的代码:
public delegate void SetHandler<T>(ref T source, object value) where T : struct;
private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo) where T : struct
{
var type = typeof(T);
DynamicMethod dm = new DynamicMethod("setter", typeof(void), new Type[] { type.MakeByRefType(), typeof(object) }, type, true);
ILGenerator setGenerator = dm.GetILGenerator();
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.DeclareLocal(type);
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldnull);
setGenerator.Emit(OpCodes.Stind_Ref);
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<T>)dm.CreateDelegate(typeof(SetHandler<>).MakeGenericType(type));
}
static void Main(string[] args)
{
MyStruct myStruct = new MyStruct();
myStruct.myField = 3;
FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance);
var setter = GetDelegate<MyStruct>(fi);
setter(ref myStruct, 111);
Console.WriteLine(myStruct.myField);
}
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.使用反射设置结构的字段或属性的最简单方法是将结构装箱,将装箱结构传递给
SetField
或SetProperty
,然后在所有所需操作完成后取消装箱结构。
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.根据 ECMA 文档,每个值类型都与两种事物相关联:存储位置类型和堆对象类型。 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.将堆对象的引用传递给
SetValue
类的方法将因此修改引用传递到的对象。
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. VB 用户注意:VB.NET 有一个非常烦人的行为,这在
Option Strict On
方言中几乎是有意义的,但即使在Option Strict Off
方言中也存在:如果编译时类型Object
的变量持有对装箱结构被分配给另一个相同类型的变量或作为Object
类型的参数传递,VB.NET 将存储或传递对原始对象副本的引用。 When writing code like the above in VB.NET, one should make obj
be of type ValueType
rather than Object
to prevent such behavior.在 VB.NET 中编写上述代码时,应将
obj
设为ValueType
而不是Object
以防止此类行为。
To add to other answers, you can actually box inside the delegate, as long as your method also returns the modified struct.要添加到其他答案中,您实际上可以在委托内装箱,只要您的方法还返回修改后的结构。
Since my IL-fu is not that great, this is how you would do it with plain reflection:因为我的 IL-fu 不是那么好,这就是你如何用简单的反射来做到这一点:
// the good side is that you can use generic delegates for added type safety
public delegate T SetHandler<T>(T source, object value);
private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo)
{
return (s, val) =>
{
object obj = s; // we have to box before calling SetValue
fieldInfo.SetValue(obj, val);
return (T)obj;
};
}
This means you will need to fetch the return value like this:这意味着您需要像这样获取返回值:
SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi);
myStruct = setter(myStruct, 111);
Console.WriteLine(myStruct.myField);
But there's no need to box it before calling the setter
.但是在调用
setter
之前不需要将它装箱。
Alternatively, you can pass the struct using the ref
keyword, which would result in:或者,您可以使用
ref
关键字传递结构,这将导致:
public delegate void SetHandler<T>(ref T source, object value);
private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo)
{
return (ref T s, object val) =>
{
object obj = s;
fieldInfo.SetValue(obj, val);
s = (T)obj;
};
}
SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi);
setter(ref myStruct, 111); // no need to return anymore
Console.WriteLine(myStruct.myField);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.