How to mutate a boxed struct using IL

后端 未结 5 1054
执笔经年
执笔经年 2020-12-24 07:41

Imagine we have a mutable struct (yes, don\'t start):

public struct MutableStruct
{
    public int Foo { get; set; }
    public override string          


        
相关标签:
5条回答
  • 2020-12-24 08:19

    I've posted a solution using Expression Trees for setting fields in another thread. It's trivial to change the code to use properties instead:

    0 讨论(0)
  • 2020-12-24 08:33

    Well, that was fun.

    Using Ldflda and Stind_* seems to work. Actually, it's mostly Unbox (see history for version that works with 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
    }
    
    0 讨论(0)
  • 2020-12-24 08:37

    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
    
    0 讨论(0)
  • 2020-12-24 08:38

    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.

    0 讨论(0)
  • 2020-12-24 08:39

    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.

    0 讨论(0)
提交回复
热议问题