C#Reflection - 如何为struct设置字段值

时间:2014-09-02 19:55:17

标签: c# reflection struct reflection.emit

如何使用DynamicMethod通过反射将值设置到struct field - myStruct.myField?当我调用setter(myStruct, 111)时未设置值,因为MyStruct是值类型。 Console.WriteLine(myStruct.myField)显示值3。
如何修改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);

5 个答案:

答案 0 :(得分:12)

编辑:I made this mistake again - 有趣的事实; unbox-any返回; unbox返回指向数据的指针 - 允许就地变异。

这是工作的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);

但是!这是一个盒装副本的变异;你需要在之后拆箱:

    object obj = myStruct;
    setter(obj, 111);
    MyStruct andBackAgain = (MyStruct)obj;
    Console.WriteLine(andBackAgain.myField);
    Console.WriteLine(myStruct.myField);

要做到这一点,您可能需要使用该方法来获取ref MyStruct,或者返回 MyStruct。你可以返回盒装副本,但这并不会让它更容易使用。坦率地说,这没有实际意义:结构通常不应该是可变的。

答案 1 :(得分:7)

我认为正如Servy在评论中指出的那样,最好不要使用可变结构,而是使用新值创建结构的副本。如果确实希望使用反射来改变结构但是它使用未记录的SetValueDirect方法来获取TypedReference

我真的不建议使用此代码;它纯粹是为了证明这是可能的:

FieldInfo

答案 2 :(得分:3)

这里的代码是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);
        }

答案 3 :(得分:2)

使用Reflection设置结构的字段或属性的最简单方法是对结构进行装箱,将装箱结构传递给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);
    }
}

根据ECMA文档,每种值类型都与两种类型相关联:存储位置类型和堆对象类型。与所有堆对象类型一样,堆对象类型将使用引用语义;将对象的引用传递给类SetValue之类的方法将修改传递引用的对象。

对VB用户的注意事项:VB.NET有一种非常讨厌的行为,在Option Strict On方言中几乎有意义,但即使在Option Strict Off方言中也存在:如果是编译时类型的变量保存对盒装结构的引用的Object被分配给同一类型的另一个变量或作为类型Object的参数传递,VB.NET将存储或传递对原始副本的引用宾语。在VB.NET中编写上述代码时,应该objValueType而不是Object以防止此类行为。

答案 4 :(得分:0)

要添加到其他答案,您实际上可以在委托中填写,只要您的方法也返回修改后的结构。

由于我的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; 
    };
}

这意味着你需要像这样获取返回值:

SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi);
myStruct = setter(myStruct, 111);
Console.WriteLine(myStruct.myField);

但是在调用setter之前无需将其打包。

或者,您可以使用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);