如何使用IL改变盒装结构

Mar*_*ell 26 .net c# metaprogramming ilgenerator

想象一下,我们有一个可变的struct(是的,不要开始):

public struct MutableStruct
{
    public int Foo { get; set; }
    public override string ToString()
    {
        return Foo.ToString();
    }
}
Run Code Online (Sandbox Code Playgroud)

使用反射,我们可以获取一个盒装实例,struct并在框内变异:

// this is basically what we want to emulate
object obj = new MutableStruct { Foo = 123 };
obj.GetType().GetProperty("Foo").SetValue(obj, 456);
System.Console.WriteLine(obj); // "456"
Run Code Online (Sandbox Code Playgroud)

我会喜欢做的是写一些IL可以做同样的,因为这-但速度更快.我是一个元编程迷; p

拆箱 - 任何值并使用常规IL改变值是微不足道的 - 但是你不能只是在之后调用它,因为这将创建一个不同的框.我我们需要做的是将它复制到现有的盒子上.我已经调查了ldobj/ stobj,但那些似乎没有做到这一点(除非我遗漏了什么).

那么:是否存在这样做的机制?或者我必须限制自己反思以执行盒装structs 的就地更新?

换句话说:什么... evil goes here...

var method = new DynamicMethod("evil", null,
    new[] { typeof(object), typeof(object) });
var il = method.GetILGenerator();
// ... evil goes here...
il.Emit(OpCodes.Ret);

Action<object, object> action = (Action<object, object>)
    method.CreateDelegate(typeof(Action<object, object>));
action(obj, 789);
System.Console.WriteLine(obj); // "789"
Run Code Online (Sandbox Code Playgroud)

Kev*_*ose 18

嗯,这很有趣.

使用LdfldaStind_*似乎工作. 实际上,它主要的Unbox(见的版本,随着工作的历史LdfldaStind_*).

这是我在LinqPad中一起攻击以证明它的原因.

public struct MutableStruct
{
    public int Foo { get; set; }

    public override string ToString()
    {
        return Foo.ToString();
    }
}

void Main()
{
    var foo = typeof(MutableStruct).GetProperty("Foo");
    var setFoo = foo.SetMethod;

    var dynMtd = new DynamicMethod("Evil", typeof(void), new [] { typeof(object), typeof(int) });
    var il = dynMtd.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);                       // object
    il.Emit(OpCodes.Unbox, typeof(MutableStruct));  // MutableStruct&
    il.Emit(OpCodes.Ldarg_1);                       // MutableStruct& int
    il.Emit(OpCodes.Call, setFoo);                  // --empty--
    il.Emit(OpCodes.Ret);                           // --empty--

    var del = (Action<object, int>)dynMtd.CreateDelegate(typeof(Action<object, int>));

    var mut = new MutableStruct { Foo = 123 };

    var boxed= (object)mut;

    del(boxed, 456);

    var unboxed = (MutableStruct)boxed;
    // unboxed.Foo = 456, mut.Foo = 123
}
Run Code Online (Sandbox Code Playgroud)

  • 圆圈现已完成! (4认同)

lep*_*pie 6

干得好:

只是用unsafe:)

static void Main(string[] args)
{
  object foo = new MutableStruct {Foo = 123};
  Console.WriteLine(foo);
  Bar(foo);
  Console.WriteLine(foo);
}

static unsafe void Bar(object foo)
{
  GCHandle h = GCHandle.Alloc(foo, GCHandleType.Pinned);

  MutableStruct* fp = (MutableStruct*)(void*)  h.AddrOfPinnedObject();

  fp->Foo = 789;
}
Run Code Online (Sandbox Code Playgroud)

IL实现留给读者练习.

更新:

基于Kevin的回答,这是一个最小的工作示例:

ldarg.0
unbox      MutableStruct
ldarg.1
call       instance void MutableStruct::set_Foo(int32)
ret
Run Code Online (Sandbox Code Playgroud)