When using LINQ it helps to be on lookout for deferred execution. With some complexity in your code you may end up running an unexpected query.
From MSDN: "deferred execution means that the evaluation of an expression is delayed until its realized value is actually required. Deferred execution can greatly improve performance when you have to manipulate large data collections, especially in programs that contain a series of chained queries or manipulations." Here's another example.
For a simple example consider a class:
class Test
{
public string Name { get; set; }
public int Priority { get; set; }
}
and a collection of those:
Test[] all = new Test[]
{
new Test { Name = "Critical", Priority = 1 },
new Test { Name = "Required", Priority = 1 },
new Test { Name = "Important", Priority = 2 },
new Test { Name = "Nice to check", Priority = 3 },
new Test { Name = "Eventually...", Priority = 3 },
};
Suppose for some reason I need to create a custom grouping of tests by priority. In the example below I save a list of LINQ queries with deferred execution:
List<IEnumerable<Test>> prioritized = new List<IEnumerable<Test>>();
for (int i = 1; i < 4; i++)
prioritized.Add(all.Where(t => t.Priority == i));
// print out the name of the first test of priority one
Console.WriteLine("The first test is " + prioritized[0].First().Name);
The call to First() above throws InvalidOperationException "Sequence contains no elements". You can check in the debugger that at the end of the for loop the prioritized list contains three empty sequences. That is because at the end of the loop the value of variable i is 4. Running the LINQ expression with i == 4 produces an empty sequence.
This is an obscure example of course. I ran into the issue while binding lists in an ASP.NET application. To avoid storing IEnumerable I could define the prioritized list as List<Test[]> or in many other ways.
But here is a way to avoid the issue using a custom function:
IEnumerable<Test> GetTestByPriority(int priority)
{
return all.Where(t => t.Priority == priority);
}
Rewriting the original loop using the function above produces expected result. It works because the query execution no longer depends on the loop variable:
for (int i = 1; i < 4; i++)
prioritized.Add(GetTestByPriority(i));
Saturday, December 19, 2009
Subscribe to:
Posts (Atom)

