Different behaviour between Join and SelectMany after replacing one of the sets
I hope that someone can shed a light on the (to me) unexpected behavioral difference between the two (result wise) equal queries.
A small program can be worth a thousand words, so here goes :static void Main(string[] args)
{
var l1 = new List<int> { 1, 2, 3 };
var l2 = new List<int> { 2, 3, 4 };
var q1 = // or var q1 = l1.Join(l2, i => i, j => j, (i, j) => i);
from i in l1
join j in l2
on i equals j
select i;
var q2 = //or var q2 = l1.SelectMany(i => l2.Where(j => i == j));
from i in l1
from j in l2
where i == j
select i;
var a1 = q1.ToList(); // 2 and 3, as expected
var a2 = q2.ToList(); // 2 and 3, as expected
l2.Remove(2);
var b1 = q1.ToList(); // only 3, as expected
var b2 = q2.ToList(); // only 3, as expected
// now here goes, lets replace l2 alltogether.
// Afterwards, I expected the same result as q1 delivered...
l2 = new List<int> { 2, 3, 4 };
var c1 = q1.ToList(); // only 3 ? Still using the previous reference to l2 ?
var c2 = q2.ToList(); // 2 and 3, as expected
}
Now I kno开发者_如何学Gow that Join internally uses a lookup class to optimize performance, and without too much knowledge, my guess is that the combination of that with captured variables might cause this behavior, but to say I really understand it, no :-)
Is this an example of what Joel calls "a leaky abstraction" ?Cheers, Bart
You're actually nearly there, given your query expansions in the comments:
var q1 = l1.Join(l2, i => i, j => j, (i, j) => i);
var q2 = l1.SelectMany(i => l2.Where(j => i == j));
Look at where l2
is used in each case. In the Join
case, the value of l2
is passed into the method immediately. (Remember that the value is a reference to the list though... changing the contents of the list isn't the same as changing the value of l2
.) Changing the value of l2
later doesn't affect what the query returned by the Join
method remembers.
Now look at SelectManay
: l2
is only used in the lambda expression... so it's a captured variable. That means that whenever the lambda expression is evaluated, the value of l2
at that moment in time is used... so it will reflect any changes to the value.
精彩评论