Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace the ref of a parameter without using the ref keyword (using IL)

I am looking to be able to replace the object reference of a parameter without having to use the ref keyword.

The reason that I am avoiding using ref is to preserve collection initializer invocation which looks for an Add(T item) method, and I need to have the collection class replace the reference with a different implementation of it's interface.

I have tried a couple of different ways to of doing this. First I tried using the undocumented keywords __makeref, __refvalue and __reftype.

Secondly I tried to create a DynamicMethod with some IL that tried to mimic what I observed from looking at a disassembled similar call with a ref parameter.

Here is some code to demonstrate:

using System;
using System.Collections.Generic;
using System.Collections;
using System.Reflection.Emit;
using System.Reflection;
interface IRecord
{
    string Name { get;}
}
class ImpA : IRecord
{
    public string Name { get { return "Implementation A"; } }
}
class ImpB : IRecord
{
    public string Name { get { return "Implementation B"; } }
}
class RecordList<T> : IEnumerable<T>
{
    //// Standard Add method (of course does not work)
    //public void Add(T item)
    //{
    //    item = (T)(object)new ImpB();
    //}

    // ref method (works great but the signature cannot be
    // used by the collection initializer)
    public void Add(ref T item)
    {
        IRecord newItem = new ImpB();
        item = (T)newItem;
    }

    //// Using System.TypedReference (does not work)
    //public void Add(T item)
    //{
    //    T newItem = (T)(object)new ImpB();
    //    TypedReference typedRef = __makeref(item);
    //    __refvalue(typedRef, T) = newItem;
    //}

    // Using Reflection.Emit DynamicMethod (This method should work but I need help)
    public void Add(T item)
    {
        IRecord newItem = new ImpB();

        System.Reflection.MethodAttributes methodAttributes =
              System.Reflection.MethodAttributes.Public
            | System.Reflection.MethodAttributes.Static;

        DynamicMethod dm = new DynamicMethod("AssignRef",
            methodAttributes,
            CallingConventions.Standard,
            null,
            new Type[] { typeof(IRecord), typeof(IRecord) },
            this.GetType(),
            true);

        ILGenerator generator = dm.GetILGenerator();
        // IL of method
        //public static void Add(ref item, ref newItem)
        //{
        //    item = newItem;
        //}
        // -- Loading Params (before call to Add() --
        //L_002b: ldloca.s sb // this is the ref variable
        //L_002d: ldloc.2 // The other variable
        // -- Add method IL --
        //L_0000: nop 
        //L_0001: ldarg.0 
        //L_0002: ldarg.1 
        //L_0003: stind.ref 
        //L_0004: ret 

        generator.Emit(OpCodes.Ldarga_S, 0);
        generator.Emit(OpCodes.Ldarg_1);
        generator.Emit(OpCodes.Stind_Ref);
        generator.Emit(OpCodes.Ret);

        Action<IRecord, IRecord> AssignRef =
            (Action<IRecord, IRecord>)dm.CreateDelegate(
            typeof(Action<IRecord, IRecord>));

        AssignRef((IRecord)item, (IRecord)newItem);
    }


    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }
    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}
class Program
{
    static void Main(string[] args)
    {
        IRecord imp = new ImpA();
        Console.WriteLine("Original implementation: {0}\n", imp.Name);

        // Calls Add Implicitly
        RecordList<IRecord> records = new RecordList<IRecord> { imp };
        // Prints "Implementation A"
        Console.WriteLine("After Add Method: {0}", imp.Name);

        records.Add(ref imp); // Explicit call with ref
        // Prints "Implementation B"
        Console.WriteLine("After Add Ref method: {0}\n", imp.Name);
    }
}

Thank you.

like image 662
RepDbg Avatar asked Feb 23 '23 11:02

RepDbg


1 Answers

I am looking to be able to replace the object reference of a parameter without having to use the ref keyword.

This is simply not going to happen; when your (non-ref) method is called, the CLR creates a copy of the passed reference, which your method receives. While your method can modify that reference to its heart's content, it has absolutely no access to the reference from which the copy was made (the reference that was passed in by the calling method) no matter what trickery you attempt using undocumented keywords or clever CIL.

Also, why you're trying to replace parameters passed into a collection initializer is beyond me. It smells of code that does things it shouldn't.

like image 62
Adam Maras Avatar answered Feb 26 '23 01:02

Adam Maras