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

时间:2014-02-01 16:45:28

标签: c# .net lambda dynamically-generated typebuilder

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

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

1 个答案:

答案 0 :(得分:10)

快速摘要

是的,你可以,但你必须做一些额外的工作。跳转到代码的代码段即可。

长答案

问题

不直接,不。正如SO Question: LambdaExpression CompileToMethod中所述,虽然.NET 4.0 中的LambdaExpression.CompileToMethod 采用MethodBuilder,但它只能表示静态方法。

部分解决方案

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

完整解决方案

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

假设TypeBuilder名为_typeBuilderMethodBuilder名为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);