Заменить ссылку параметра без использования ключевого слова ref (используя IL)

Я хочу заменить ссылку на объект параметра без использования ключевого слова ref.

Причина, по которой я избегаю использования ref, заключается в том, чтобы сохранить вызов инициализатора коллекции, который ищет метод Add(T item), и мне нужно, чтобы класс коллекции заменил ссылку другой реализацией своего интерфейса.

Я пробовал несколько разных способов сделать это. Сначала я попытался использовать недокументированные ключевые слова __makeref, __refvalue и __reftype.

Во-вторых, я попытался создать DynamicMethod с некоторым IL, который пытался имитировать то, что я наблюдал, глядя на дизассемблированный аналогичный вызов с параметром ref.

Вот код для демонстрации:

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);
    }
}

Спасибо.


person RepDbg    schedule 26.07.2011    source источник
comment
Ты пытаешься сделать что-то действительно уродливое. Вы уверены, что вам это нужно/хотите?   -  person Henk Holterman    schedule 27.07.2011
comment
Фрагмент кода, который я разместил, предназначен только для демонстрации концепции, которая помогает объяснить мой вопрос. Я понимаю, что это может быть использовано для создания чего-то уродливого - спасибо.   -  person RepDbg    schedule 27.07.2011


Ответы (1)


Я хочу заменить ссылку на объект параметра без использования ключевого слова ref.

Этого просто не произойдет; при вызове вашего (не-ref) метода CLR создает копию переданной ссылки, которую получает ваш метод. Хотя ваш метод может изменить эту ссылку в соответствии со своим содержанием, он не имеет абсолютно никакого доступа к ссылке, из которой была сделана копия (ссылке, которая была передана вызывающим методом), независимо от того, какие уловки вы используете. попробуйте использовать недокументированные ключевые слова или хитрый CIL.

Кроме того, я не понимаю, почему вы пытаетесь заменить параметры, переданные в инициализатор коллекции. Пахнет кодом, который делает то, чего не должен делать.

person Adam Maras    schedule 26.07.2011
comment
Спасибо за ответ - я думал, что это можно сделать в управляемом С++ (я ошибаюсь?). Так как же это может быть ограничением CLR? Если я использую интерфейс, почему переключение реализации должно быть проблемой? - person RepDbg; 27.07.2011
comment
Я не знаю языка, который позволяет функциям изменять нессылочный параметр. Это невозможно сделать в .NET (поскольку это не поддерживается CLR), что означает, что это невозможно сделать в Managed C++ или C++/CLI. - person Adam Maras; 27.07.2011
comment
Как указывает этот ответ, заявленная цель ОП по своей сути бессмысленна. Сама причина, по которой вещи передаются по ссылке, заключается в том, чтобы... ну, разрешить ссылку на них. - person Glenn Slayden; 05.06.2021