27

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

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

使用反射,我们可以获取 this 的盒​​装实例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 对任何值进行拆箱并改变值是微不足道的 - 但您不能只是在之后调用 box 它,因为这会创建一个不同的盒子。我我们需要在这里做的是将它复制到现有的盒子上。我已经调查过ldobj/ stobj,但那些似乎并没有完成这项工作(除非我遗漏了一些东西)。

那么:是否存在这样做的机制?还是我必须将自己限制在反射以执行 boxed s 的就地更新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"
4

5 回答 5

19

嗯,那很有趣。

使用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
}
于 2013-09-21T22:44:25.197 回答
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 实现留给读者作为练习。

更新:

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

ldarg.0
unbox      MutableStruct
ldarg.1
call       instance void MutableStruct::set_Foo(int32)
ret
于 2013-09-21T22:31:19.123 回答
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();
    }
}

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

于 2013-09-23T08:59:12.387 回答
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-parameter(或out值类型构造函数中的-parameter,这就是为什么this不能捕获到闭包中,就像任何方法中的ref/out参数一样)并且可以修改。

当对未装箱的值调用结构实例方法时,this赋值将有效地替换调用站点的值。当在装箱实例上调用实例方法时(如上例通过虚拟调用或接口调用),- refparameter 指向 box 对象内部的值,因此可以修改装箱值。

于 2013-09-21T23:23:40.840 回答
0

我已经发布了一个使用表达式树在另一个线程中设置字段的解决方案。将代码更改为使用属性是微不足道的:

于 2015-08-08T20:19:06.307 回答