Deferred execution can be a lifesaver with LINQ but it can have a downside as well.
In this example we set up a LINQ query and then iterate through the results twice. The output may not be quite what we anticipated or want,
int counter = 0; var evenNumbersInSeries = Enumerable.Range(0, 10).Select( x => { int result = x + counter; counter++; return result; } ); // List the numbers in the series Console.WriteLine("First Try:\n"); foreach(int i in evenNumbersInSeries) { Console.WriteLine(i); } // List them again Console.WriteLine("\nSecond Try:\n"); foreach(int i in evenNumbersInSeries) { Console.WriteLine(i); }
The output for these two runs is shown in the two columns below:
0 10 2 12 4 14 6 16 8 18 10 20 12 22 14 24 16 26 18 28
Oops. The Counter maintained its state, and since we don’t evaluate until the foreach loop, nothing causes the counter to reset.
We could solve this problem in two ways. One is to reset the counter before running the second loop. Another way to solve this is to freeze the evaluation by asking LINQ to convert the results (currently in the variable evenNumberInSeries to an array. The result would be that the entire series would be evaluated and placed in the array and then we’d iterate through the array:
int counter = 0; var evenNumbersInSeries = Enumerable.Range(0, 10).Select( x => { int result = x + counter; counter++; return result; } ).ToArray(); Console.WriteLine("First Try:\n"); foreach(int i in evenNumbersInSeries) { Console.WriteLine(i); } Console.WriteLine("\nSecond Try:\n"); foreach(int i in evenNumbersInSeries) { Console.WriteLine(i); }
Because ToArray forces the results into an array, the entire series is evaluated before the foreach loops run, and the foreach loops iterate over the array. The array has the (unchanging) results and so the two itereations are identical,
0 0 2 2 4 4 6 6 8 8 10 10 12 12 14 14 16 16 18 18