Resharper's example code for explaining "Possible multiple enumeration of IEnumerable"
Asked Answered
S

5

87

Sometimes Resharper warns about:

Possible multiple enumeration of IEnumerable

There's an SO question on how to handle this issue, and the ReSharper site also explains things here. It has some sample code that tells you to do this instead:

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

My question is about this specific suggestion: won't this still result in enumerating through the collection twice in the 2 for-each loops?

Sension answered 21/11, 2013 at 18:28 Comment(1)
Does this answer your question? Handling warning for possible multiple enumeration of IEnumerableCalibrate
C
197

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.

Chaplet answered 21/11, 2013 at 18:32 Comment(6)
No. The GetEnumerator() method will only be invoked once in the foreach loop. The real reason is for the risk of dirty data. For example, in GetNames(), there is a SQL query, but only query which returns IEnurable. When you invoke .ToList(), you store all the data in memory, there is little risk of dirty data. But if there is much time between 2 loop oprations, if you excute SQL to database every time, then there is a big risk of dirty data.Myrmeco
@SunRobin this is an answer that presents the truth in simplified form, as is also mentioned in it. I haven't gotten around yet to improve it. Your use of "dirty data" may require some further explanation.Chaplet
This is the url from Microsoft to tell us the inside implementation of foreach loop. You can see that the GetEnumerator() is only invoked once. And another thing we need to know is that IEnumerable is lazy loading when it is working with some ORM. msdn.microsoft.com/en-us/library/aa664754(v=vs.71).aspx If you just get the handler of Enumerator and not load all the data into memory, you may have some conflicts between the 2 operations with the same enumerator, someone has modified the database. To avoid this, Resharper suggest you to load the data into Merroy through .ToList() method.Myrmeco
This answer is somewhat imprecise. Not every IEnumerable are evaluated for each enumeration, only those who are implemented with 'deferred execution' (That is is the link which you were lacking, @CodeCaster). The R# warning can be safely ignored when the underlying implementation is not deferred executed, as with this question example which calls ToList() to store it in another IEnumerable variable.Lancinate
@Frédéric I know, and I've excused myself before. It's on my TODO-list to improve this post. :)Chaplet
@CodeCaster, no problem, I was coming from a recent answer linking there, and I added in it a brief explanation of what was lacking imo. Feel free to reuse it if you like. Then I will probably delete my comments here once their wile be no more relevant.Lancinate
M
11

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/

Misprint answered 18/3, 2015 at 13:0 Comment(1)
You could point out special points of your link here.. but the link is nice --> +1Asthma
S
10

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.

Sapphera answered 14/5, 2014 at 5:47 Comment(0)
F
5

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().

Flimsy answered 21/11, 2013 at 18:32 Comment(0)
T
1

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

Tropophilous answered 7/3, 2021 at 7:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.