简体   繁体   English

C# 反射 - 如何为结构设置字段值

[英]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.使用反射设置结构的字段或属性的最简单方法是将结构装箱,将装箱结构传递给SetFieldSetProperty ,然后在所有所需操作完成后取消装箱结构。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM