Как преобразовать выражение в метод экземпляра MethodBuilder?

Я пытаюсь создать тип во время выполнения через TypeBuilder. Я генерирую методы экземпляра типа с помощью MethodBuilder, однако я не хочу генерировать il через IlGenerator.Emit; вместо этого я хотел бы создать выражение, представляющее метод, поэтому я могу преобразовать его в MethodBuilder для метода экземпляра.

Это возможно? Если да, то как преобразовать выражение в метод экземпляра MethodBuilder?


person zastrowm    schedule 01.02.2014    source источник


Ответы (1)


Краткое резюме

Да, вы можете, но вам придется проделать дополнительную работу. Перейдите к фрагменту кода, чтобы сделать это.

Длинный ответ

Эта проблема

Не напрямую, нет. Как отмечено в SO Question: LambdaExpression CompileToMethod, в то время как LambdaExpression.CompileToMethod в .NET 4.0 делает возьмите MethodBuilder, он может представлять только статический метод.

Частичное решение

Итак, вам нужно обойти это ограничение, сначала создав ссылку на статический метод, а затем создав метод экземпляра, который вызывает этот статический метод. Если в вашем выражении нет никаких «живых объектов» (т. е. вы используете существующую ссылку на объект при создании выражения), то довольно просто создать статический метод, а затем создать метод экземпляра, который вызывает статический метод. . Однако, если в вашем выражении есть "живой объект", CompileToMethod сообщит вам, что не может использовать выражение, потому что в вашем выражении есть живой объект.

Полное решение

Вместо создания статического метода вы можете добавить поле делегата к сгенерированному типу, а затем из метода экземпляра вызвать поле делегата и передать аргументы метода делегату.

Code

Предположим, что TypeBuilder называется _typeBuilder, MethodBuilder называется methodBuilder, а делегат перенаправляется на имя delegateToInvoke:

// create a field to hold the dynamic delegate
var fieldBuilder = _typeBuilder.DefineField(
  "<>delegate_field",
  delegateToInvoke.GetType(),
  FieldAttributes.Private);

// remember to set it later when we create a new instance
_fieldsToSet.Add(new KeyValuePair<FieldInfo, object>(fieldBuilder, delegateToInvoke));

var il = methodBuilder.GetILGenerator();

// push the delegate onto the stack
il.Emit(OpCodes.Ldarg_0);
// by loading the field
il.Emit(OpCodes.Ldfld, fieldBuilder);

// if the delegate has a target, that means the first argument is really a pointer to a "this"
// object/closure, and we don't want to forward it. Thus, we skip it and continue as if it
// wasn't there.
if (delegateToInvoke.Target != null)
{
  parameters = parameters.Skip(1).ToArray();
}

// push each argument onto the stack (thus "forwarding" the arguments to the delegate).
for (int i = 0; i < parameters.Length; i++)
{
  il.Emit(OpCodes.Ldarg, i + 1);
}

// call the delegate and return
il.Emit(OpCodes.Callvirt, delegateToInvoke.GetType().GetMethod("Invoke"));
il.Emit(OpCodes.Ret);

Когда вы создаете новый экземпляр, обязательно установите поле перед использованием экземпляра:

generatedType.GetField("<>delegate_field", BindingFlags.NonPublic | BindingFlags.Instance)
             .SetValue(instance, delegateToInvoke);
person zastrowm    schedule 01.02.2014
comment
Это заслуживает того, чтобы за него проголосовали; не потому, что решение красивое, а потому, что это может быть один из ваших единственных вариантов. Единственное другое возможное решение, которое я нашел, отмечено здесь и делает неудобное использование интерфейса (хотя и позволяет избежать эмиссии IL): почти так же круто, как я и надеялся/" rel="nofollow noreferrer">justinmchase.com/2009/01/08/ - person Special Sauce; 11.11.2017