Подтвердить что ты не робот

Как мутировать коробку с использованием 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"
4b9b3361

Ответ 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, поэтому можно изменить значение в коробке.