为什么编译器不能通过内联来优化闭包变量?

Why can't the compiler optimize closure variable by inlining?

我有一个这样的Main方法:

static void Main(string[] args)
{
     var b = new byte[1024 * 1024];

     Func<double> f = () =>
     {
         new Random().NextBytes(b);
         return b.Cast<int>().Average();
     };

     var avg = f();
     Console.WriteLine(avg);
}

由于我在此处访问局部变量 b,因此编译器会创建一个 class 来捕获该变量,并且 b 成为该 class 的字段。然后 b 的寿命与编译器生成的 class 的寿命一样长,这会导致内存泄漏。即使 b 超出范围(可能不是在这种情况下,但可以想象这是在另一个方法内部而不是 Main),字节数组也不会被释放。

我想知道的是,既然我在声明 Func 之后没有在任何地方访问或修改 b,为什么编译器不能内联那个局部变量而不用创建一个 class?像这样:

Func<double> f = () =>
{
    var b = new byte[1024 * 1024];
    new Random().NextBytes(b);
    return b.Cast<int>().Average();
};

我在 Debug 和 Release 模式下编译了这段代码,在这两种模式下都生成了 DisplayClass:

这只是没有作为优化实现,还是我遗漏了什么?

Is this just not implemented as an optimization or is there anything I am missing?

对于您给出的 具体 示例,您可能不想进行该代码转换,因为它会改变程序的语义。如果 new 抛出异常,在原始程序中它应该在委托执行之前抛出异常,而在您的转换中,副作用会被延迟。这是否是一个应该保留的重要 属性 值得商榷。 (这样做也会给调试器带来问题;调试器已经必须假装闭包 类 的元素是包含方法体的局部变量,而这种优化可能会使它进一步复杂化。)

不过,更一般的一点是有关系的。如果您知道封闭变量仅用于其值,则可以进行许多优化。

当我在编译器团队时——我于 2012 年离开——我和 Neal Gafter 考虑实施此类优化,以及一些更复杂的优化,旨在降低昂贵对象生命周期被延长的可能性一不小心太长了

旁白:更复杂的场景中最简单的是:我们有两个 lambda 转换为委托;一个存储在一个短暂的变量中,并在包含对昂贵对象的引用的局部变量上关闭;一个存储在一个长期存在的变量中,并在引用廉价对象的局部变量上关闭。昂贵的对象与长寿命变量一样长,即使它没有被使用。更一般地,可以将多个闭包构造为基于封闭关系的分区;当时我们只根据嵌套来划分闭包;同一嵌套级别的闭包是一个闭包。给定的情况很少见,并且有明显的解决方法,但如果它根本没有发生就好了。

我们没有这样做是因为在我们实施 Roslyn 期间有更多重要的优化和功能,我们不想给已经很长的计划增加风险。

我们可以自信地执行此类优化,因为在 C# 中很容易知道何时为局部变量设置了别名,因此您可以确定它是否在创建闭包后被写入。

不知道这些优化有没有同时实现;可能不会。

我也不知道编译器是否对 C# 7 本地函数进行了此类优化,但我怀疑答案是 "yes"。试试本地函数,看看会发生什么!