Roman A. Taycher Roman A. Taycher - 3 months ago 11
Python Question

Why isn't IEnumerable consumed?/how do generators work in c# compared to python

So I thought I understood c# yield return as being largely the same as pythons yield which I thought that I understood. I thought that the compiler transforms a function into an object with a pointer to where execution should be resumed and when a request for the next value comes along the object runs up to the next yield where it updates the pointer of where to resume execution and returns a value.

In python this works sort of similarly to lazy evaluation in that it produces values as needed but once the values are used once they can be gc'ed if not save in another variable. Trying to iterate over the result of such a function twice returns an empty iterable unless you transform it to a list.

ex.

def y():
list = [1,2,3,4]

for i in list:
yield str(i)

ys = y()
print "first ys:"
print ",".join(ys)
print "second ys:"
print ",".join(ys)


outputs

first ys:
1,2,3,4
second ys:


Until recently I thought the same thing was true for c# but trying it out in dotnetfiddle failed.

http://dotnetfiddle.net/W5Cbv6

using System;
using System.Linq;
using System.Collections.Generic;

public class Program
{
public static IEnumerable<string> Y()
{
var list = new List<string> {"1","2","3","4","5"};
foreach(var i in list)
{
yield return i;
}
}

public static void Main()
{


var ys = Y();
Console.WriteLine("first ys");
Console.WriteLine(string.Join(",", ys));
Console.WriteLine("second ys");
Console.WriteLine(string.Join(",", ys));

}
}


outputs

first ys
1,2,3,4,5
second ys
1,2,3,4,5


What is happening here? Is it caching the result? It can't be right, otherwise File.ReadLines would blow up on huge files? Is it simply restarting the function from the top a second time?

note: I'm a bit uncertain about some of the terminology of generators and coroutines so I've tried to avoid labelling.

Answer

You're very close. An IEnumerable is an object capable of creating an iterator (an IEnumerator). An IEnumerator behaves exactly as you've described.

So the IEnumerable generates generators.

Unless you go out of your way to generate some sort of state shared between the generated iterators, IEnumerator objects won't affect each other, whether they are from separate calls to the iterator block or another IEnumerator generated by the same IEnumerable.