如何使用IL改变盒装结构

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

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

使用reflection,我们可以获取此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 action = (Action) method.CreateDelegate(typeof(Action)); action(obj, 789); System.Console.WriteLine(obj); // "789" 

嗯,这很有趣。

使用LdfldaStind_*似乎有效。 实际上,它主要是Unbox (请参阅与LdfldaStind_*一起使用的版本的历史记录 )。

这是我在LinqPad中一起攻击以certificate它的原因。

 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)dynMtd.CreateDelegate(typeof(Action)); var mut = new MutableStruct { Foo = 123 }; var boxed= (object)mut; del(boxed, 456); var unboxed = (MutableStruct)boxed; // unboxed.Foo = 456, mut.Foo = 123 } 

干得好:

只是使用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 

你可以更容易地做到这一点。 在我们有动态的.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(); } } 

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

即使没有不安全的代码,纯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参数一样)并且可以被修改。

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

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