22

我有一个项目列表和一个 LINQ 查询。现在,使用 LINQ 的延迟执行,后续的 foreach 循环是只执行一次查询还是在循环中的每个回合执行一次?

鉴于此示例(取自MSDN 上的 Introduction to LINQ Queries (C#)

    // The Three Parts of a LINQ Query: 
    //  1. Data source. 
    int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

    // 2. Query creation. 
    // numQuery is an IEnumerable<int> 
    var numQuery =
        from num in numbers
        where (num % 2) == 0
        select num;

    // 3. Query execution. 
    foreach (int num in numQuery)
    {
        Console.Write("{0,1} ", num);
    }

或者,换句话说,如果我有,会有什么不同

    foreach (int num in numQuery.ToList())

而且,如果底层数据不在数组中,而是在数据库中,这有关系吗?

4

4 回答 4

20

现在,使用 LINQ 的延迟执行,后续的 foreach 循环是只执行一次查询还是在循环中的每个回合执行一次?

是的,一次循环。实际上,它执行查询的次数可能少于一次——您可以在中途中止循环,并且(num % 2) == 0不会对任何剩余的项目执行测试。

或者,换句话说,如果我有:

foreach (int num in numQuery.ToList())

两个区别:

  1. 在上面的例子中,ToList()浪费了时间和内存,因为它首先做与 initial 相同的事情foreach,从它构建一个列表,然后foreachs 那个列表。根据结果​​的大小,差异将介于微不足道和阻止代码正常工作之间。

  2. 但是,如果您要对相同的结果重复执行foreach或以其他方式重复使用它,则 then whileforeach只运行一次查询,下一个foreach再次运行它。如果查询很昂贵,那么该ToList()方法(并存储该列表)可以节省大量资金。

于 2012-11-06T12:03:15.680 回答
10

不,没有区别。in表达式被计算一次。更具体地说,foreach构造调用表达式GetEnumerator()上的方法in并重复调用MoveNext()和访问Current属性以遍历IEnumerable.

OTOH,打电话ToList()是多余的。你不应该费心打电话给它。

如果输入是数据库,则情况略有不同,因为 LINQ 输出IQueryable,但我很确定foreach仍然将其视为IEnumerableIQueryable继承)。

于 2012-11-06T12:00:26.943 回答
2

正如所写,循环的每次迭代都会做与获取下一个结果所需的工作量完全相同的工作。因此,从技术上讲,答案将是“以上都不是”。该查询将“分段”执行。

如果您使用ToList()或任何其他物化方法(ToArray()等),那么查询将在现场评估一次,后续操作(例如迭代结果)将仅在“哑”列表上工作。

如果numbers是一个IQueryable而不是一个IEnumerable- 因为它可能是在数据库场景中 - 那么上述内容仍然接近事实,尽管不是一个完全准确的描述。特别是,在第一次尝试实现结果时,可查询提供者将与数据库对话并生成结果集;然后,将在每次迭代中提取此结果集中的行。

于 2012-11-06T12:00:36.797 回答
1

linq 查询将在枚举时执行(作为.ToList()调用的结果或foreach对结果执行操作)。

如果您枚举 linq 查询的结果两次,这两次都会导致它查询数据源(在您的示例中,枚举集合),因为它本身只返回一个IEnumerable. 但是,根据 linq 查询,它可能并不总是枚举整个集合(例如.Any().Single()如果有 a ,它将在第一个对象或第一个匹配对象上停止.Where())。

linq 提供程序的实现细节可能不同,因此当数据源是数据库时,通常的行为是直接调用.ToList()查询cache结果并确保执行查询(在 EF、L2S 或 NHibernate 的情况下)一次,然后而不是在稍后在代码中的某个点枚举集合时,并防止在多次枚举结果时多次执行查询。

于 2012-11-06T12:15:53.857 回答