Imagine we have a mutable struct
(yes, don't start):
public struct MutableStruct
{
public int Foo { get; set; }
public override string ToString()
{
return Foo.ToString();
}
}
Using reflection, we can take a boxed instance of this struct
and mutate it inside the box:
// 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"
What I would like to do is to write some IL that can do the same as this - but faster. I'm a meta-programming junkie ;p
It is trivial to unbox-any the value and mutate the value using regular IL - but you can't just call box it afterwards because that will create a different box. I'm guessing that what we would need to do here is copy it over the existing box. I have investigated ldobj
/ stobj
, but those don't seem to do the job (unless I'm missing something).
So: does a mechanism to do this exist? Or must I limit myself to reflection to perform in-place updates of boxed struct
s ?
Or in other words: what ... 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"
Well, that was fun.
Using
Actually, it's mostly Unbox (see history for version that works with Ldflda
and Stind_*
seems to work.Ldflda
and Stind_*
).
Here's what I hacked together in LinqPad to prove it out.
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
}
Here you go:
Just use 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 implementation is left as an exercise to the reader.
Update:
Based on the Kevin's answer, here is a minimal working example:
ldarg.0
unbox MutableStruct
ldarg.1
call instance void MutableStruct::set_Foo(int32)
ret
You can do this even easier. Try this under .NET 4.5 where we have dynamic.
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();
}
}
I know it's not reflection but still fun though.
Even without unsafe code, pure 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
}
}
In C#, this
reference inside value types instance methods actually is ref
-parameter (or out
-parameter in value type constructor, and that is why this
can't be captured into closures, just like ref
/out
parameters in any methods) and can be modified.
When struct instance method is invoked on unboxed value, this
assignment will effectively replace value at the call site. When instance method is invoked on boxed instance (via virtual call or interface call like in the example above), ref
-parameter is pointed to the value inside the box object, so it is possible to modify boxed value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With