我希望有人可以了解(对我而言)两个(结果明智的)平等查询之间出乎意料的行为差异。
一个小程序可以胜过千言万语,所以这里有:
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
}
现在我知道Join内部使用查找类来优化性能,而且没有太多的知识,我的猜测是它与捕获的变量的组合可能会导致这种行为,但是说我真的理解它,不: )
这是乔尔称之为“漏洞抽象”的一个例子吗?
干杯, 巴特
答案 0 :(得分:2)
考虑到您在评论中的查询扩展,您实际上几乎就在那里:
var q1 = l1.Join(l2, i => i, j => j, (i, j) => i);
var q2 = l1.SelectMany(i => l2.Where(j => i == j));
查看每种情况下使用l2
的位置。在Join
的情况下,l2
的值会立即传递给方法。 (请记住,该值是对列表的引用,但...更改列表的内容与更改l2
的值不同。)稍后更改l2
的值不会t会影响Join
方法返回的查询所记录的内容。
现在看看SelectManay
:l2
仅用于lambda表达式...所以它是捕获的变量。这意味着无论何时评估lambda表达式,都会使用当时的l2
的值...因此它将反映对值的任何更改。