如何将Expression转换为MethodBuilder实例方法?

Fri*_*Guy 5 .net c# lambda dynamically-generated typebuilder

我试图通过TypeBuilder在运行时生成一个类型.我正在使用MethodBuilder生成该类型的实例方法,但是我不想生成il via IlGenerator.Emit; 相反,我想创建一个表示方法的表达式,所以我可以将它转换为MethodBuilder的实例方法.

这可能吗?如果是这样,我如何将Expression转换为MethodBuilder实例方法?

Fri*_*Guy 12

快速摘要

是的,你可以,但你必须做一些额外的工作.跳转到代码的代码段,以执行此操作.

答案很长

问题

不直接,没有.如SO问题中所述:LambdaExpression CompileToMethod,虽然LambdaExpression.CompileToMethod在.NET 4.0中确实采用了a MethodBuilder,但它只能表示静态方法.

部分解决方案

那么,您需要首先创建静态方法引用来解决此限制,然后创建一个调用该静态方法的实例方法.如果表达式中没有任何"活动对象"(即,在创建表达式时使用现有对象引用),则创建静态方法然后创建调用静态方法的实例方法非常简单.但是,如果表达式中有"活动对象",CompileToMethod则会通知您它无法使用表达式,因为表达式中有活动对象.

完整的解决方案

您可以改为向生成的类型添加委托字段,然后从实例方法调用委托字段并将方法参数转发给委托,而不是创建静态方法.

假设有一个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);
Run Code Online (Sandbox Code Playgroud)

创建新实例时,请务必在使用实例之前设置该字段:

generatedType.GetField("<>delegate_field", BindingFlags.NonPublic | BindingFlags.Instance)
             .SetValue(instance, delegateToInvoke);
Run Code Online (Sandbox Code Playgroud)