Resharper 解释“ IEnumable 的可能多枚举”的示例代码

Resharper 有时会警告:

IEnumable 的可能多重枚举

还有 关于如何处理这个问题的问题,ReSharper 网站也解释了 给你的内容。它有一些示例代码告诉您这样做:

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

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

55956 次浏览

GetNames() returns an IEnumerable. So if you store that result:

IEnumerable foo = GetNames();

Then every time you enumerate foo, the GetNames() method is called again (not literally, I can't find a link that properly explains the details, but see IEnumerable.GetEnumerator()).

Resharper sees this, and suggests you to store the result of enumerating GetNames() in a local variable, for example by materializing it in a list:

IEnumerable fooEnumerated = GetNames().ToList();

This will make sure that the GetNames() result is only enumerated once, as long as you refer to fooEnumerated.

This does matter because you usually want to enumerate only once, for example when GetNames() performs a (slow) database call.

Because you materialized the results in a list, it doesn't matter anymore that you enumerate fooEnumerated twice; you'll be iterating over an in-memory list twice.

Yes, you'll be enumerating it twice with no doubt. but the point is if GetNames() returns a lazy linq query which is very expensive to compute then it will compute twice without a call to ToList() or ToArray().

GetNames() is not called twice. The implementation of IEnumerable.GetEnumerator() is called each time you want to enumerate the collection with foreach. If within the IEnumerable.GetEnumerator() some expensive calculation is made this might be a reason to consider.

I found this to have the best and easiest way to understand multiple enumerations.

C# LINQ: Possible Multiple Enumeration of IEnumerable

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

Just because a method returns IEnumerable doesn't mean there will be deferred execution.

E.g.

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...
}

Back to the question, if:

a. There is deferred execution (e.g. LINQ - .Where(), .Select(), etc.): then the method returns a "promise" that knows how to iterate over the collection. So when calling .ToList() this iteration happens and we store the list in memory.

b. There is no deferred execution (e.g. method returns a List): then assuming GetNames returns a list, it's basically like doing a .ToList() on that list

var names = GetNames().ToList();
//          1        2 3
  1. Yolo Prints out
  2. List is returned
  3. ReturnedList.ToList() is called

PS, I left the following comment on Resharper's documentation

Hi,

Can you please make it clear in the documentation that this'd only be an issue if GetNames() implements deferred execution?

For example, if GetNames() uses yield under the hood or implements a deferred execution approach like most LINQ statements for example (.Select(), .Where(), etc.)

Otherwise, if under the hood GetNames() is not returning an IEnumerable that implements defered execution, then there is no performance or data integrity issues here. E.g. if GetNames returns List