Cómo mutar una estructura en caja usando IL

Imagina que tenemos una struct mutable (sí, no empieces):

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

Usando la reflexión, podemos tomar una instancia en caja de esta struct y mutarla dentro de la caja:

 // 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" 

Lo que me gustaría hacer es escribir un IL que pueda hacer lo mismo, pero más rápido. Soy un adicto a la meta-progtwigción; p

Es trivial desempaquetar cualquier valor y mutar el valor usando IL normal, pero no puede simplemente seleccionarlo después porque eso creará un cuadro diferente . Supongo que lo que deberíamos hacer aquí es copiarlo en el cuadro existente. He investigado ldobj / stobj , pero esos no parecen hacer el trabajo (a menos que me esté perdiendo algo).

Entonces, ¿existe un mecanismo para hacer esto? ¿O debo limitarme a la reflexión para realizar actualizaciones in situ de struct caja?

O en otras palabras: ¿qué ... 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" 

Bueno eso fue divertido.

Usar Ldflda y Stind_* parece funcionar. En realidad, se trata principalmente de Unbox (consulte el historial de la versión que funciona con Ldflda y Stind_* ).

Esto es lo que piratearon juntos en LinqPad para demostrarlo.

 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 } 

Aqui tienes:

Solo usa 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; } 

La implementación de IL se deja como un ejercicio para el lector.

Actualizar:

Basado en la respuesta de Kevin, aquí hay un ejemplo de trabajo mínimo:

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

Puedes hacerlo incluso más fácil. Intenta esto bajo .NET 4.5 donde tenemos dinámica.

 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(); } } 

Sé que no es un reflection, pero sigue siendo divertido.

Incluso sin código inseguro, puro 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 } } 

En C #, this referencia dentro de los métodos de instancia de tipos de valor en realidad es ref -parameter (o out -parameter en el constructor de tipo de valor, y es por eso que this no se puede capturar en cierres, como out parámetros de ref / out en cualquier método) ser modificado.

Cuando el método de instancia de estructura se invoca en un valor sin caja, this asignación reemplazará efectivamente el valor en el sitio de la llamada. Cuando se invoca el método de instancia en una instancia en caja (mediante una llamada virtual o una llamada de interfaz como en el ejemplo anterior), ref -parameter se señala al valor dentro del objeto de caja, por lo que es posible modificar el valor en caja.

He publicado una solución utilizando los árboles de expresiones para configurar campos en otro hilo . Es trivial cambiar el código para usar propiedades en su lugar: