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

Cyr*_*rus 12 c# reflection struct reflection.emit

如何myStruct.myField使用DynamicMethod进行反射,将值设置为struct字段?当我调用setter(myStruct, 111)value时没有设置,因为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);
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 12

编辑:我再次犯了这个错误 - 有趣的事实; 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);
Run Code Online (Sandbox Code Playgroud)

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

    object obj = myStruct;
    setter(obj, 111);
    MyStruct andBackAgain = (MyStruct)obj;
    Console.WriteLine(andBackAgain.myField);
    Console.WriteLine(myStruct.myField);
Run Code Online (Sandbox Code Playgroud)

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

  • @petelids公平,我只修复了OP的IL,但是:是的,我做*很多IL - 对于元编程来说非常强大.我在protobuf-net,dapper,fast-member和其他一些库中使用它. (2认同)

pet*_*ids 7

我认为正如Servy在评论中指出的那样,最好不要使用可变结构,而是使用新值创建结构的副本.如果你真的想使用反射来改变结构,你可以SetValueDirectFieldInfo实例上使用该方法,但它使用未记录的方法来获取.__makerefTypedReference

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

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
Run Code Online (Sandbox Code Playgroud)


sup*_*cat 7

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

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

VB 用户注意:VB.NET 有一个非常烦人的行为,这在Option Strict On方言中几乎是有意义的,但即使在方言中也存在:如果将保存对装箱结构的引用的Option Strict Off编译时类型的变量分配给另一个变量Object相同类型或作为 类型的参数传递Object,VB.NET 将存储或传递对原始对象副本的引用。在 VB.NET 中编写类似上述的代码时,应该使用obj类型ValueType而不是Object阻止这种行为。