从方法体中提取代码并使用表达式内联执行它?

时间:2016-01-18 00:15:57

标签: .net lambda expression-trees

以此代码为例,创建一个调用名为AddNumbers的方法的自定义委托:

public static int AddNumbers(int a, int b)
{
    return a + b;
}

static void Main(string[] args)
{
    ParameterExpression
        arga = Expression.Parameter(typeof(int)),
        argb = Expression.Parameter(typeof(int));

    MethodCallExpression result = Expression.Call(typeof(Program_ModelBinding).GetMethod("AddNumbers"), arga, argb);

    var deli = Expression.Lambda(result, arga, argb).Compile() as Func<int, int, int>;

    Console.WriteLine(deli(2, 4));
}

编译为:

.Lambda #Lambda1<System.Func`3[System.Int32,System.Int32,System.Int32]>(
    System.Int32 $var1,
    System.Int32 $var2) {
    .Call ModelBinding.Program_ModelBinding.AddNumbers(
        $var1,
        $var2)
}

但是,为了减少干扰并保持堆栈整洁,我希望能够内联AddNumbers方法,以便它的方法体在代码本身内编译,就好像我这样做:

BinaryExpression result = Expression.Add(arga, argb);

那将编译为:

.Lambda #Lambda1<System.Func`3[System.Int32,System.Int32,System.Int32]>(
    System.Int32 $var1,
    System.Int32 $var2) {
    $var1 + $var2
}

我还要注意,即使方法是非静态的,我宁愿不必处理对象实例。我只想提取方法体,然后在我的代码中内联使用它。

我不知怎的认为System.Reflection.Emit命名空间有我想要的答案,但我不知道要继续哪个方向。

2 个答案:

答案 0 :(得分:1)

没有简单的方法可以做到这一点。

C#编译器将您的方法编译为IL并将其转换回表达式是可能的,但是复杂且成功的程度可以变化。您可以使用现有反编译器的代码来帮助(例如ILSpy是开源的),甚至可能有一个库来执行此操作(我发现的唯一内容是this old library)。

但我的猜测是,所有这些都不值得为了“减少干扰并保持堆栈整洁”。

答案 1 :(得分:0)

一种简单的方法是以编程方式反汇编AddNumbers函数,然后使用该输出驱动Reflection.Emit以动态创建内联代码。

但是,您可能希望某种方法在Main(或接收内联代码的任何方法)中拥有占位符来保存发出的代码。换句话说,您可能不希望发出所有Main,您希望编译器创建除AddNumbers的内联代码之外的所有内容。 (但是你可以对整个Main方法应用相同的反编译/重新编译方法)。

但问题是,需要保留多少空间?如果您内联的代码与AddNumbers一样简单,那就不是问题了。您可以使用内联代码替换原始方法调用,因为它会更短。但是如果内联代码甚至比AddNumbers更复杂,那么内联代码需要更多的空间,并且需要一种方法来让编译器留下足够的空间。

但是随着内联代码变得越来越长,内联的节省变得越来越小,所以也许你可能只对代码的大小有一个硬性限制 - 任何内联代码都必须小于限制。

我想提供一个更好的答案,你需要提供更多关于这个问题的信息:

您是否希望将编译器生成的代码和内联代码混合在一个方法中?或者是一个单独的,动态创建的方法吗?

如果是前者会有一个站点来保存内联代码吗?或者会有不止一个,但固定的,少量的网站?

您预计代码内联的复杂程度(在IL指令数量上)会是多少?

动态生成和编译的C#代码是一个选项吗?根据你对其他问题的回答,Reflection.Emit可能不是一个好的选择 - 相反,你可能需要编译器的智能。