【问题标题】:C# Reflection - How to set field value for structC# 反射 - 如何为结构设置字段值
【发布时间】:2014-09-02 19:55:17
【问题描述】:

如何使用 DynamicMethod 通过反射将值设置为结构字段 - 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);

【问题讨论】:

  • 这就是我们不使用可变结构的原因。你正在改变结构的副本。创建一个新版本的结构,其中的字段已初始化为您想要的。
  • 相关set a field of struct 问题可能是你所追求的......旁注:很好的问题和很好的样本......但你想要实现的整个目标是非常令人困惑,在大多数情况下不太可能按照您想要的方式工作......
  • 糟糕;我意识到我搞砸了我的答案;已编辑 - 但是,作为 ref MyStruct 会更好

标签: c# reflection struct reflection.emit


【解决方案1】:

编辑: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 的方法。您可以退回盒装副本,但这并不能使其更易于使用。坦率地说,它没有实际意义:结构通常不应该是可变的。

【讨论】:

  • @petelids 公平地说,我刚刚修复了 OP 的 IL,但是:是的,我做了 很多 的 IL - 对于元编程非常强大。我在 protobuf-net、dapper、fast-member 和其他一些库中使用它。
  • 编辑的答案有效! andBackAgain.myField 为 111。但 myStruct.myField 始终为 3。是否有另一种方法可以在不使用 ref MyStruct 的情况下使用高性能反射来设置 myStruct.myField 的值?
  • @cyrus ref MyStructTypedReference 是唯一不会创建副本的东西
  • @MarcGravell 你能用ref 写一个解决方案吗?我可以使用没有任何泛型类型的委托! public delegate void SetHandler(ref object source, object value),但是对于这个委托,我得到一个错误:Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type
  • @Cyrus ref object 帮不了你;这是同样的问题;它必须ref MyStructMyStruct*TypedReference,如果你想避免“变异克隆”的事情。但同样,大多数结构不应该是可变的无论如何
【解决方案2】:

我认为正如 Servy 在 cmets 中指出的那样,最好不要使用可变结构,而是使用新值创建结构的副本。如果您真的想要使用反射来改变结构,但它使用未记录的__makeref 方法来获取TypedReference,则可以在FieldInfo 实例上使用SetValueDirect 方法。 p>

我真的不推荐使用这段代码;这纯粹是为了表明这是可能的:

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

【讨论】:

  • 这是什么我什至不知道... +1 对于__makeref 关键字,我不敢相信我从未偶然发现过。你只是给了我一堆玩的东西。
  • @Groo 还有更多未记录的关键字。 __arglist__refvalue__reftype。只是谷歌:)
  • 谢谢@Groo - 我想我在Eric Lippert's 博客的某个地方看到了它,它一定是卡住了(抱歉,我找不到直接链接)。除了玩之外,我从来没有用过它,我想我永远不会:)
  • @petelids:是的,我刚找到this pearl,看起来很酷。
  • @Groo - 这是一个非常有趣的链接。今晚我应该做一些工作,但我想我可能只是在那个链接上浪费了一两个小时!
【解决方案3】:

使用反射设置结构的字段或属性的最简单方法是对结构进行装箱,将装箱的结构传递给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 中编写上述代码时,应将 obj 设为 ValueType 而不是 Object 类型以防止此类行为。

【讨论】:

  • 如何更新你的方法,如果输入是 struct: public static void test(S1 s) ... but not use (ref S1 s)
  • 您的意思是“如何使方法更新外部结构:public static void test(ref S1 s),但 注意 使用 (ref S1 s)?我可能打错了“不” “注意”的次数比我记得的要多,但当然它完全改变了意思。
【解决方案4】:

这里是带有 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);
        }

【讨论】:

  • 有效!带有 ref 参数的好解决方案。
  • 你能写一个没有泛型委托的解决方案吗?我可以使用委托public delegate void SetHandler(ref object source, object value)
【解决方案5】:

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

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

【讨论】:

  • 我有很多结构,一个方法 void SetStructValue(object someStruct, object value)。如何在我的方法 SetStructValue 中调用您的方法 GetDelegate。我无法调用 GetDelegate(fi),因为 someStruct 的类型与 typeof(MyStruct) 不同。 typeof someStruct 可以是everythig - 任何结构!
  • 如果您的结构已经装箱到object,那么使用泛型没有意义,您可以使用其他答案中显示的示例。但是如果你在调用SetStructValue 之前有一个值类型变量,那么你也应该考虑将该方法设为泛型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多