Does foreach execute the query only once?

2019-01-18 09:44发布

问题:

I have a list of items and a LINQ query over them. Now, with LINQ's deferred execution, would a subsequent foreach loop execute the query only once or for each turn in the loop?

Given this example (Taken from Introduction to LINQ Queries (C#), on MSDN)

    // 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);
    }

Or, in other words, would there be any difference if I had:

    foreach (int num in numQuery.ToList())

And, would it matter, if the underlying data is not in an array, but in a Database?

回答1:

Now, with LINQ's deferred execution, would a subsequent foreach loop execute the query only once or for each turn in the loop?

Yes, once for the loop. Actually, it may execute the query less than once - you could abort the looping part way through and the (num % 2) == 0 test wouldn't be performed on any remaining items.

Or, in other words, would there be any difference if I had:

foreach (int num in numQuery.ToList())

Two differences:

  1. In the case above, ToList() wastes time and memory, because it first does the same thing as the initial foreach, builds a list from it, and then foreachs that list. The differences will be somewhere between trivial and preventing the code from ever working, depending on the size of the results.

  2. However, in the case where you are going to repeatedly do foreach on the same results, or otherwise use it repeatedly, the then while the foreach only runs the query once, the next foreach runs it again. If the query is expensive, then the ToList() approach (and storing that list) can be a massive saving.



回答2:

No, it makes no difference. The in expression is evaluated once. More specifically, the foreach construct invokes the GetEnumerator() method on the in expression and repeatedly calls MoveNext() and accesses the Current property in order to traverse the IEnumerable.

OTOH, calling ToList() is redundant. You shouldn't bother calling it.

If the input is a database, the situation is slightly different, since LINQ outputs IQueryable, but I'm pretty sure that foreach still treats it as an IEnumerable (which IQueryable inherits).



回答3:

As written, each iteration of the loop would do exactly as much work as it needed to fetch the next result. So the answer would technically be "none of the above". The query would execute "in pieces".

If you use ToList() or any other materialization method (ToArray() etc) then the query will be evaluated once on the spot and subsequent operations (such as iterating over the results) will simply work on a "dumb" list.

If numbers were an IQueryable instead of an IEnumerable -- as it would likely be in a database scenario -- then the above is still close to the truth although not a perfectly accurate description. In particular, on the first attempt to materialize a result the queryable provider would talk to the database and produce a result set; then, rows from this result set would be pulled on each iteration.



回答4:

The linq query will be executed when it is enumerated (either as the result of a .ToList() call or doing a foreach over the results.

If you are enumerating the results of the linq query twice, both times will cause it to query the data source (in your example, enumerating the collection) as it is itself only returning an IEnumerable. However, depending on the linq query, it may not always enumerate the entire collection (e.g .Any() and .Single() will stop on the first object or the first matching object if there is a .Where()).

The implementation details of a linq provider may differ so the usual behaviour when the data source is a database is to call .ToList() straight away to cache the results of the query & also ensure that the query (in the case of EF, L2S or NHibernate) is executed once there and then rather than when the collection is enumerated at some point later in the code and to prevent the query being executed multiple times if the results are enumerated multiple times.



标签: c# linq tolist