Как мутировать коробку с использованием IL
Представьте, что мы имеем mutable 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
Тривиально для unbox-any значение и мутировать значение с помощью обычного 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"
Ответы
Ответ 1
Ну, это было весело.
Использование Ldflda
и Stind_*
похоже на работу.
На самом деле, это главным образом Unbox (см. историю для версии, которая работает с Ldflda
и 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
}
Ответ 2
Здесь вы идете:
Просто используйте 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 остается в качестве упражнения для читателя.
Update:
Основываясь на ответе Кевина, вот минимальный рабочий пример:
ldarg.0
unbox MutableStruct
ldarg.1
call instance void MutableStruct::set_Foo(int32)
ret
Ответ 3
Вы можете сделать это еще проще. Попробуйте это в .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();
}
}
Я знаю, что это не отражение, но все равно забавно.
Ответ 4
Даже без небезопасного кода, чистый С#:
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
}
}
В С#, this
ссылка внутри значений типов методы экземпляра на самом деле являются ref
-параметром (или out
-параметром в конструкторе типа значения, и именно поэтому this
невозможно зафиксировать в закрытии, просто как ref
/out
в любых методах) и может быть изменен.
Когда метод экземпляра struct вызывается по незанятому значению, назначение this
будет эффективно заменять значение на сайте вызова. Когда метод экземпляра вызывается в экземпляре в штучной упаковке (через виртуальный вызов или вызов интерфейса, как в приведенном выше примере), ref
-параметр указывает на значение внутри объекта box, поэтому можно изменить значение в коробке.
Ответ 5
Я разместил решение используя деревья выражений для установки полей в другом потоке. Тривиально изменить код для использования свойств: