Reflection.Emit — IL — вызов метода на объекте

Я работаю над созданием динамического метода во время выполнения для копирования объекта. Предположим:

class Source
{
   public List<int> L1 {get;set;}
}

class Dest
{
   public List<int> L1 {get;set;}
}

Теперь эта ситуация работает корректно. Я получаю Source.L1 и устанавливаю Dest.L1. Я делаю это со следующим IL:

        generator.Emit(OpCodes.Newobj, constructor);
        generator.Emit(OpCodes.Dup);
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Callvirt, miGetter);
        generator.Emit(OpCodes.Callvirt, miSetter);
        generator.Emit(OpCodes.Ret);

Все это работает нормально... теперь самое сложное. Давайте изменим Dest на:

class Dest
{
   private List<int> _l1 = new List<int>();
   public List<int> L1 {get { return _l1; } }
}

Теперь то, что я хочу сделать в этом случае, это вызвать Dest.L1.Clear(), а затем Dest.L1.AddRange(...).

Я даже не могу заставить работать .Clear.

У меня еще будет:

        generator.Emit(OpCodes.Newobj, constructor);

        // this block is repeated 5 times for various properties

        generator.Emit(OpCodes.Dup);
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Callvirt, miGetter);
        generator.Emit(OpCodes.Callvirt, miSetter);

        // List property will be copied here
        // miGetter = Dest.L1.Get
        // TODO
        // end list property

        generator.Emit(OpCodes.Ret);

Как мне настроить IL в блоке TODO? Я попытался выполнить dup/loadArg0/call miGetter/call miClear, но это дало мне недопустимую программу.


person SledgeHammer    schedule 07.04.2016    source источник
comment
Почему вы хотите позвонить Clear? ты не создаешь новый Dest? При строительстве он будет пуст.   -  person Yacoub Massad    schedule 08.04.2016
comment
См. 2-ю версию Dest ниже. L1 теперь доступна только для чтения, поэтому сеттер вызывать нельзя (ну можно, но не следует)   -  person SledgeHammer    schedule 08.04.2016
comment
Это не мой вопрос. Когда вы создаете новый Dest, список L1 пуст. Вам нужно позвонить AddRange, но не обязательно сначала звонить Clear.   -  person Yacoub Massad    schedule 08.04.2016
comment
Опять же, я предлагаю вам написать свой код на языке высокого уровня и использовать декомпилятор, чтобы получить действительные инструкции IL для ваших операций. Были ли у вас какие-либо проблемы с этим подходом?   -  person thehennyy    schedule 08.04.2016
comment
@YacoubMassad, да, я понимаю, что когда вы обновите Dest, список будет пустым ... если только конструктор не добавит в него что-то ... крайний случай, просто на всякий случай :).   -  person SledgeHammer    schedule 08.04.2016
comment
@thehennyy - да, я только что вспомнил ваш совет, LOL ... по какой-то причине VS просто создает Dup, а не Ldarg, так что теперь это работает.   -  person SledgeHammer    schedule 08.04.2016


Ответы (1)


Вот рабочий пример с объяснением того, что происходит со стеком оценки на каждом шаге:

DynamicMethod method =
    new DynamicMethod("Test", typeof(Dest), new Type[] { typeof(Source) });

var generator = method.GetILGenerator();

var constructor = typeof(Dest).GetConstructor(Type.EmptyTypes);

var miGetter = typeof(Source).GetProperty("L1").GetMethod;

var miDestGetter = typeof(Dest).GetProperty("L1").GetMethod;

var addRange = typeof(List<int>).GetMethod("AddRange");

var clear = typeof(List<int>).GetMethod("Clear");

generator.Emit(OpCodes.Newobj, constructor);//Stack: DestObject

generator.Emit(OpCodes.Dup);//Stack: DestObject,DestObject

generator.Emit(OpCodes.Call, miDestGetter);//Stack: DestObject,DestObject.L1

generator.Emit(OpCodes.Dup);//Stack: DestObject,DestObject.L1,DestObject.L1

generator.Emit(OpCodes.Call, clear);//Stack: DestObject,DestObject.L1

generator.Emit(OpCodes.Ldarg_0);//Stack: DestObject,DestObject.L1,SourceObject

generator.Emit(OpCodes.Call, miGetter);//Stack: DestObject,DestObject.L1,SourceObject.L1

generator.Emit(OpCodes.Call, addRange);//Stack: DestObject

generator.Emit(OpCodes.Ret);

var function = (Func<Source, Dest>)method.CreateDelegate(typeof(Func<Source, Dest>));

Source source = new Source
{
    L1 = new List<int>() { 1, 2, 3 }
};

var result = function(source);
person Yacoub Massad    schedule 07.04.2016