如何使用IL改变盒装结构

时间:2013-09-21 21:40:43

标签: c# .net metaprogramming ilgenerator

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

public struct MutableStruct
{
    public int Foo { get; set; }
    public override string ToString()
    {
        return Foo.ToString();
    }
}

使用反射,我们可以获取此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"

喜欢做的是写一些可以做到这一点的IL - 但速度更快。我是一个元编程瘾君子; p

取消包装任何值并使用常规IL改变值是微不足道的 - 但你不能只是在之后调用它,因为这会创建一个不同的框。我猜测我们需要做的是将它复制到现有的盒子上。我调查了ldobj / stobj,但这些似乎没有完成任务(除非我遗漏了一些东西)。

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

或换句话说:... 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"

5 个答案:

答案 0 :(得分:18)

嗯,这很有趣。

使用LdfldaStind_*似乎有效。 实际上,它主要是Unbox(见history for version that works with Ldflda and Stind_*)。

这是我在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
}

答案 1 :(得分: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;
}

IL的实施留给了读者。

<强>更新

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

ldarg.0
unbox      MutableStruct
ldarg.1
call       instance void MutableStruct::set_Foo(int32)
ret

答案 2 :(得分:2)

您可以更轻松地完成此操作。在我们有动态的.NET 4.5下试试这个。

struct Test
{
    public Int32 Number { get; set; }


    public override string ToString()
    {
        return this.Number.ToString();
    }
}


class Program
{
    static void Main( string[] args )
    {
        Object test = new Test();

        dynamic proxy = test;

        proxy.Number = 1;

        Console.WriteLine( test );
        Console.ReadLine();
    }
}

我知道这不是反思,但仍然很有趣。

答案 3 :(得分:1)

即使没有不安全的代码,纯粹的C#:

using System;

internal interface I {
  void Increment();
}

struct S : I {
  public readonly int Value;
  public S(int value) { Value = value; }

  public void Increment() {
    this = new S(Value + 1); // pure evil :O
  }

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

class Program {
  static void Main() {
    object s = new S(123);
    ((I) s).Increment();
    Console.WriteLine(s); // prints 124
  }
}

在C#中,this引用内部值类型实例方法实际上是ref - 参数(或out - 值类型构造函数中的参数,这就是为什么this可以不会被捕获到闭包中,就像任何方法中的ref / out参数一样)并且可以修改。

在未装箱的值上调用struct instance方法时,this赋值将有效替换调用站点的值。当在盒装实例上调用实例方法时(通过上面示例中的虚拟调用或接口调用),ref - 参数指向box对象内的值,因此可以修改盒装值。

答案 4 :(得分:0)

我发布了solution using Expression Trees for setting fields in another thread。将代码更改为使用属性是非常简单的: