Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use one variable in multiple chained LINQ queries

Is this fair to use a single lambda expression variable in multiple chained calls? For example:

MyList.Where(i => i.ID > 20).OrderBy(i => i.Name);

So does i used in Where() remain independent of the i used in OrderBy() or can they have some hidden side-effects on each other, so that I must used different variable for each? Also, does your answer hold for VB.NET too?

I'm asking this because I have read in a slightly different context that I should not use foreach variable in LINQ queries directly and instead make a local copy of the variable inside the loop. Is there some similar effect hidden in the above code too?

like image 330
dotNET Avatar asked Dec 06 '22 02:12

dotNET


2 Answers

They are completely independent. Actually, each time you declare a lambda you declare also range variables, which are local to this labda expression. So the i in the Where(i => i.ID > 20) is completely different from the i in OrderBy(i => i.Name). In the first case i refers to the random element of MyList, and then i refers to the random element of the sequence that comes from the Where clause, which would be the sequence of all the elements of MyList that have an ID>20.

like image 195
Christos Avatar answered Dec 08 '22 14:12

Christos


To elaborate a bit more, this is fine:

MyList.Where(i => i.ID > 20).OrderBy(i => i.Name);

Those two variables called i are completely separate and only exist within the context of their respective lambda expressions. However, this is not:

int i = 0;
MyList.Where(i => i.ID > 20).OrderBy(i => i.Name);

Now those i's in your lambdas conflict with the i defined in the parent scope.

The issue with foreach is a little more subtle and slightly tangential to your original question. If you have this:

foreach (var foo in fooList) 
{
    var filteredList = MyList.Where(i => i.ID > foo.Id).OrderBy(i => i.Name);
}

The problem here is because LINQ uses deferred execution and references the loop variable foo so it will create a closure to include foo. The problem is, it doesn't copy foo it actually has a reference to the variable. So when you finally execute your lambda's by iterating it, or:

var bar = filteredList.ToList();

The value of foo in your Where lambda will be the value of foo right now, not the value when the lambda was declared. So foo will always be the last item from fooList. Copying the variable fixes this problem because now it will close over that distinct variable (that only exists for that one iteration of the loop) rather than the loop variable.

foreach (var foo in fooList) 
{
    var copy = foo;
    var filteredList = MyList.Where(i => i.ID > copy.Id).OrderBy(i => i.Name);
}
like image 36
Matt Burland Avatar answered Dec 08 '22 15:12

Matt Burland