C#为什么Except和Where Enumerable给出了这个奇怪的结果?

时间:2015-04-23 09:21:56

标签: c# enumerator ienumerator

我一直在调试这个生产错误,我迫不及待地寻求帮助,对我来说也很有趣。

我简化了代码逻辑并为调试添加了一些打印输出:

int[] a = { 2,2,2 };
var b = a.Where(x => x==2);

for(int i = 0; i < 3; i++)
{
    var c = b.Where(x => x==i);

    Console.WriteLine("iter {0} before - B Count: {1}, C Count: {2}", i, b.Count(), c.Count());

    if (c.Count() != b.Count())
        b = b.Except(c);

    Console.WriteLine("iter {0} after - B Count: {1}, C Count: {2}", i, b.Count(), c.Count());
}

Console.WriteLine("After Loop: B Count: {0}", b.Count());

有趣的是(奇怪地),输出是:

iter 0 before - B Count: 3, C Count: 0
iter 0 after - B Count: 1, C Count: 0
iter 1 before - B Count: 1, C Count: 0
iter 1 after - B Count: 1, C Count: 0
iter 2 before - B Count: 0, C Count: 0
iter 2 after - B Count: 0, C Count: 0
After Loop: B Count: 1

问题1:

为什么b.Count() == 0在&#34;之前2&#34;。唯一的事情发生在&#34; iter 1之后和iter 2之前&#34;是

var c = b.Where(x => x==i);

为什么这段代码会改变b?

问题2:

为什么b.Count()在循环结束后变回1?

非常感谢大家的帮助指导我解决这个问题,谢谢!

4 个答案:

答案 0 :(得分:4)

由于linq的懒惰评估,每次调用Where时都会执行ExceptCount()次调用。这里需要注意的重要一点是,i的值不会被记住,而是会将i的当前值用于所有之前的Where次调用。

  

为什么b.Count()== 0 at“iter 2 before”

此时,i已增加到2。当您调用count时,将使用此值评估整个linq查询,这意味着之前添加的“Except”调用已经删除了所有内容!

  

为什么b.Count()在循环结束后变回1?

i等于3时,循环结束。现在,当您评估整个linq查询时,.Where(x => x==i)个调用都不会返回任何内容,这反过来意味着没有Except调用将删除任何内容(重复项除外)。

答案 1 :(得分:3)

对于 1 :&#34;为什么此代码会改变b?&#34;

它没有。添加(在循环开始时,c之前):

Console.WriteLine("iter {0} before anything - B Count: {1}", i, b.Count());

b没有变化。

对于 2 :由于捕获的i而导致 2 。改为:

for(int tmp = 0 ; tmp < 3 ; tmp++) {
      int i = tmp;
    // ...
}

然后再试一次。现在i的范围循环中,并相应地捕获(for循环中的迭代器变量的范围循环之外,复杂的原因;在foreach循环中,迭代器变量的范围是在循环内部还是外部取决于您正在使用的C#版本。

答案 2 :(得分:1)

Except就像它的SQL版本一样,它可以消除重复。因此,它的行为就像在列表中调用Distinct一样。

所以当你第一次执行Except

if (c.Count() != b.Count()) // c.Count() == 0, b.Count() == 3
    b = b.Except(c);

您只能获得b的唯一元素,其中只有一个元素。

答案 3 :(得分:0)

Enumerable.Except通过使用默认的相等比较器来比较值来产生两个序列的集合差异。集合不包含重复项。

这就是为什么序列在Except之后只包含一个2。

int[] onlyOneTwo = new[]{ 2,2,2 }.Except(Enumerable.Empty<int>()).ToArray();

因此,除Except blueUsers($db, $core, ...)之外,空序列不会删除任何内容。