83

有时 Resharper 会警告:

IEnumerable 可能的多重枚举

一个关于如何处理这个问题的 SO question,ReSharper 网站也在这里解释了一些事情。它有一些示例代码告诉您这样做:

IEnumerable<string> names = GetNames().ToList();

我的问题是关于这个具体建议:这是否仍会导致在 2 个 for-each 循环中对集合进行两次枚举?

4

5 回答 5

193

GetNames()返回一个IEnumerable. 因此,如果您存储该结果:

IEnumerable foo = GetNames();

然后每次枚举foo时,都会再次调用该GetNames()方法(不是字面意思,我找不到正确解释细节的链接,但请参阅IEnumerable.GetEnumerator())。

Resharper 看到了这一点,并建议您将枚举结果GetNames()存储在局部变量中,例如通过在列表中实现它:

IEnumerable fooEnumerated = GetNames().ToList();

这将确保GetNames()结果只枚举一次,只要您引用fooEnumerated.

这确实很重要,因为您通常只想枚举一次,例如在GetNames()执行(慢速)数据库调用时。

因为您在列表中具体fooEnumerated了结果,所以您枚举两次并不重要。您将遍历内存列表两次。

于 2013-11-21T18:32:37.480 回答
11

我发现这是理解多个枚举的最佳和最简单的方法。

C# LINQ:IEnumerable 的可能多重枚举

https://helloacm.com/c-linq-possible-multiple-enumeration-of-ienumerable-resharper/

于 2015-03-18T13:00:51.237 回答
9

GetNames()不会被调用两次。IEnumerable.GetEnumerator()每次您想用 枚举集合时都会调用的实现foreach。如果进行IEnumerable.GetEnumerator()一些昂贵的计算,这可能是一个需要考虑的理由。

于 2014-05-14T05:47:54.867 回答
5

是的,毫无疑问,您将枚举它两次。但关键是如果GetNames()返回一个计算起来非常昂贵的惰性 linq 查询,那么它将计算两次而不调用ToList()or ToArray()

于 2013-11-21T18:32:59.730 回答
0

仅仅因为一个方法返回 IEnumerable 并不意味着会有延迟执行。

例如

IEnumerable<string> GetNames()
{
    Console.WriteLine("Yolo");
    return new string[] { "Fred", "Wilma", "Betty", "Barney" };
}

var names = GetNames(); // Yolo prints out here! and only here!

foreach(name in names)
{
    // Some code...
}

foreach(name in names)
{
    // Some code...
}

回到问题,如果:

一个。存在延迟执行(例如 LINQ - .Where()、.Select() 等):然后该方法返回一个知道如何迭代集合的“承诺”。因此,当调用 .ToList() 时,会发生这种迭代,我们将列表存储在内存中。

湾。没有延迟执行(例如方法返回一个列表):然后假设 GetNames 返回一个列表,它基本上就像在该列表上执行 .ToList()

var names = GetNames().ToList(); 
//          1        2 3
  1. Yolo 打印出来
  2. 返回列表
  3. ReturnedList.ToList() 被调用

PS,我在Resharper 的文档上留下了以下评论

你好,

您能否在文档中明确说明,如果 GetNames() 实现延迟执行,这只会是一个问题?

例如,如果 GetNames() 在后台使用 yield 或像大多数 LINQ 语句一样实现延迟执行方法,例如(.Select()、.Where() 等)

否则,如果 GetNames() 没有返回实现延迟执行的 IEnumerable,则此处不存在性能或数据完整性问题。例如,如果 GetNames 返回 List

于 2021-03-07T07:09:01.103 回答