我在这里两次迭代这个可枚举的吗?

时间:2013-04-23 14:51:32

标签: c# algorithm optimization

考虑以下代码块:

using (FileStream fs = new FileStream(@"C:\bad_records.txt", 
                                      FileMode.Create, 
                                      FileAccess.Write))
{
    var badEnumerable = _cache.Where(kvp => !kvp.Value.Item1);

    fs.WriteLine(string.Format("BAD  RECORDS ({0})", badEnumerable.Count()));
    fs.WriteLine("==========");

    foreach (var item in badEnumerable)
    {
        fs.WriteLine(string.Format("{0}: {1}", item.Key, item.Value.Item2));
    }
}

其中_cache的定义如下:

static Dictionary<string, Tuple<bool, string, string>> _cache;

我是否将此枚举重复两次?一次使用Count()一次使用foreach

5 个答案:

答案 0 :(得分:8)

是的,你正在迭代可枚举两次。

测试此方法的一种简单方法是使用辅助方法,例如:

private static int count = 0;
public static IEnumerable<T> CountIterations<T>(IEnumerable<T> sequence)
{
    count++;
    //or some other debug type logging
    Console.WriteLine("Iterated {0} times.", count);
    foreach(var item in sequence)
        yield return item;
}

答案 1 :(得分:3)

您将字典_cache用作IEnumerable的{​​{1}} 方法推迟执行的方法 因此,您需要对其进行两次枚举:KeyValuePairCount

您可以将其更改为:
foreach
var badEnumerable = _cache.Where(kvp => !kvp.Value.Item1).ToArray();

答案 2 :(得分:2)

是的,.Count()foreach都会导致_cache枚举两次,并根据Where原因中的谓词进行验证。

关于这是否是一个问题取决于许多事情:

  1. 内存中_cache中的所有值是否已经存在,或者是否正在查询基础源(如数据库)。
  2. 查询集合中有多少项目,比较成本是多少。
  3. 可以多次安全地枚举来源。
  4. 例如,如果_cache中的值已经在内存中并且谓词是一个简单的布尔属性比较,那么枚举缓存两次可能更有效并且不会增加额外的内存开销,而添加{{1}仍然会产生2个枚举(.ToList()之一和列表中的一个)但是谓词检查只会发生一次(在_cache调用中)并且.ToList()将有更少的对象枚举,但你会增加附加列表的额外内存开销。

    如果缓存来自数据库,则在foreach之后添加.ToList()的内存开销几乎肯定会比对数据库执行2次单独查询更好。

答案 3 :(得分:1)

简短的回答是肯定的。

根据badEnumerable的基础类型,可能枚举两次。这是由于所谓的“deferred execution”。延迟执行意味着您的LINQ查询实际上并未执行“直到查询变量在foreach或For Each循环中迭代”(MSDN)。您的foreach语句显然是对变量的迭代,Enumerable.Count()也执行迭代(在本例中)。

但在某些情况下,这不会导致两次迭代。这发生在badEnumerable is actually a subclass of ICollection时。在这种情况下,调用.Count()实际上引用了基础.Count属性,并且枚举它。

由于badEnumerableDictionary<TKey, TValue>,并且因为对Enumerable.Where()的调用会返回通用IEnumerable(不是ICollection),特定情况不会遇到这种情况,并会迭代两次。

答案 4 :(得分:0)

是的,为避免重复两次,请使用List 您可以在初始迭代后打印计数。 另一种解决方案是保存循环中写入的文本,并在打印计数后将其打印出来。

编辑更正:

using (FileStream fs = new FileStream(@"C:\bad_records.txt", FileMode.Create, FileAccess.Write))
{
    var badEnumerable = _cache.Where(kvp => !kvp.Value.Item1);

    int count = 0;
    foreach (var item in badEnumerable)
    {
        count++;
        Console.WriteLine(string.Format("{0}: {1}", item.Key, item.Value.Item2));
    }

    Console.WriteLine("==========");
    Console.WriteLine(string.Format("BAD  RECORDS ({0})", count));
}