为什么C#编译器会对这个嵌套的LINQ查询感到生气?


97

尝试编译以下代码,您会发现编译器需要> 3 GB的RAM(计算机上的所有可用内存)和很长的时间进行编译(实际上,我在10分钟后得到IO异常)。

using System;
using System.Linq;

public class Test
{
    public static void Main()
    {
        Enumerable.Range(0, 1).Sum(a =>
        Enumerable.Range(0, 1).Sum(b =>
        Enumerable.Range(0, 1).Sum(c =>
        Enumerable.Range(0, 1).Sum(d =>
        Enumerable.Range(0, 1).Sum(e =>
        Enumerable.Range(0, 1).Sum(f =>
        Enumerable.Range(0, 1).Count(g => true)))))));
    }
}

有人可以解释这种奇怪的行为吗?

CS版本:Microsoft(R)Visual C#编译器版本4.0.30319.17929
操作系统名称:Microsoft Windows 7 Ultimate
操作系统版本:6.1.7601 Service Pack 1 Build 7601

内存使用情况


5
好决定!我只是将代码粘贴到Visual Studio中,它消耗了允许32位进程访问的所有4Gb内存,然后崩溃了(Windows 8.1上的2013 Ultimate)。
satnhak 2014年

2
将此代码添加到共享代码库(使用记事本),并观察您的同事计算机崩溃。
usr 2014年

3
如果Microsoft Connect和Roslyn小组的编译器表现出相同的行为,则向他们报告是一件好事。
Trillian 2014年

3
我相信我已经听过Eric Lippert在某个地方(尽管我不记得在哪里)说过,将la​​mbda嵌套太多并带有类型推断会导致编译器有些麻烦。我无法想像我在哪里看过,因此无法引用。希望这个人自己可以看到这个并发表评论……
Chris

2
做得好,将其缩减,您可能会对此有一个不错的答案:崩溃您最喜欢的编译器
Nathan Cooper 2014年

Answers:


40

我认为这与类型推断和/或lambda生成(当类型推断必须与正常方向相反)有关,并与重载解析结合在一起。不幸的是,仅提供类型参数并不能解决这种情况(可能仍然需要执行类型检查)。

在分析了lambda之后,以下代码(在逻辑上应与您的代码等效)在编译时不会出现问题:

static void Main()
{
    var x = Enumerable.Range(0, 1).Sum(a);
}

private static int a(int a)
{
    return Enumerable.Range(0, 1).Sum(b);
}
private static int b(int b)
{
    return Enumerable.Range(0, 1).Sum(c);
}
private static int c(int c)
{
    return Enumerable.Range(0, 1).Sum(d);
}
private static int d(int d)
{
    return Enumerable.Range(0, 1).Sum(e);
}
private static int e(int e)
{
    return Enumerable.Range(0, 1).Sum(f);
}
private static int f(int f)
{
    return Enumerable.Range(0, 1).Count(g);
}
private static bool g(int g)
{
    return true;
}

我相信Eric Lippert在C#编译器中的某些类型之一(某些问题)可能会迫使编译器尝试解决NP-Complete问题之前发布过类型推断,而它的唯一真正策略(如此处)是蛮力的。如果可以找到相关参考,请在此处添加它们。


我可以找到的最佳参考文献是Eric 这里讨论的事实,这是导致实际成本增加的重载解析工作-请记住,Enumerable.Sum有10个重载接受lambda /方法。


1
因此,基本上,编译器通过10^n组合来强行执行他的方式(其中n链式方法的数量)。听起来很合理(作为一种解释)。
DarkWanderer 2014年

1
@ DarkWanderer:that^numberofpossibletypes
嬉皮

@Damien_The_Unbeliever,我了解您的想法,接缝确实很合理(通过代码无法编译的方式
Eugene D. Gubenkov 2014年

15
您的分析在这里是正确的。每个lambda都会引入十个可能的过载因素,因此必须检查所有组合。我考虑过添加一些代码,当组合数量变大时却获得了成功,但是最终并没有实现它。
埃里克·利珀特

2
@EricLippert是时候提出请求了!:D
罗布H
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.