有时,Resharper会发出警告:
可能多次枚举IEnumerable
这个问题在Stack Overflow上有解决方案,ReSharper网站这里也有相关说明。它提供了一些示例代码,并建议您改为这样做:
IEnumerable<string> names = GetNames().ToList();
我的问题是关于这个具体的建议:这样做是否仍会导致在2个for-each循环中枚举整个集合两次?
有时,Resharper会发出警告:
可能多次枚举IEnumerable
这个问题在Stack Overflow上有解决方案,ReSharper网站这里也有相关说明。它提供了一些示例代码,并建议您改为这样做:
IEnumerable<string> names = GetNames().ToList();
我的问题是关于这个具体的建议:这样做是否仍会导致在2个for-each循环中枚举整个集合两次?
GetNames()
返回一个 IEnumerable
。所以如果你将其结果存储在变量中:
IEnumerable foo = GetNames();
每次枚举 foo
时,都会再次调用 GetNames()
方法(并非字面意义上的“再次调用”,我找不到一个能很好地解释细节的链接,但请参见IEnumerable.GetEnumerator()
)。
Resharper会看到这一点,并建议您将GetNames()
的枚举结果存储在本地变量中,例如通过将其实例化为列表:
IEnumerable fooEnumerated = GetNames().ToList();
只要您引用 fooEnumerated
, 这将确保只枚举一次 GetNames()
的结果。
这很重要,因为通常您只想枚举一次,例如当 GetNames()
执行(缓慢的)数据库调用时。
由于您在列表中 实例化了 结果,现在不再重要您两次枚举 fooEnumerated
; 您将对一个内存中的列表进行两次迭代。
IEnumerable
都在每次枚举时被评估,只有那些使用了“延迟执行”实现的对象才会被评估(这就是你所缺少的链接,@CodeCaster)。当底层实现没有被延迟执行时,例如此问题示例中调用了ToList()
将其存储在另一个IEnumerable
变量中,可以安全地忽略R#警告。 - Frédéric我认为这篇文章提供了最好、最容易理解多个枚举的方法。
C# LINQ: 可能会枚举IEnumerable多次
https://helloacm.com/c-linq-possible-multiple-enumeration-of-ienumerable-resharper/
GetNames()
不会被调用两次。每当你想使用 foreach
枚举集合时,都会调用 IEnumerable.GetEnumerator()
的实现。如果在 IEnumerable.GetEnumerator()
中进行了一些昂贵的计算,这可能是需要考虑的一个原因。
是的,你肯定会重复枚举两次。但关键是,如果GetNames()
返回一个非常昂贵的惰性Linq查询,那么它将在没有调用ToList()
或ToArray()
的情况下计算两次。
仅因为一个方法返回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...
}
回到问题,如果:
a. 存在延迟执行(例如LINQ -.Where(),.Select()等):那么该方法返回一个“承诺”,知道如何迭代集合。因此,当调用.ToList()时,这种迭代发生并将列表存储在内存中。
b. 不存在延迟执行(例如方法返回一个列表):那么假设GetNames返回一个列表,那么基本上就像在该列表上执行.ToList()。
var names = GetNames().ToList();
// 1 2 3
PS,我在Resharper的文档上留下了以下评论:
您好,
请在文档中明确说明只有当GetNames()实现延迟执行时才会出现问题。
例如,如果GetNames()在底层使用yield或者像大多数LINQ语句一样实现了延迟执行(.Select(),.Where()等),那么这将是一个问题。
否则,如果GetNames()在底层没有返回实现延迟执行的IEnumerable,那么这里就没有性能或数据完整性问题。例如,如果GetNames返回List。