Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens to GetEnumerator() method when yield return is used?

Learning the collections and IEnumerable and IEnumerator interfaces. I have the below program. WHen i step into the

IEnumerator<string> name = sample.GetEnumerator();

it calls the Console.WriteLine("inside getenumerator");

class Program
    {
        static void Main(string[] args)
        {
            SampleStrings sample = new SampleStrings();
            IEnumerator<string> name = sample.GetEnumerator();
            foreach (var item in sample)
            {
                Console.WriteLine(item);
            }
            Console.ReadLine();
        }
    }
    class SampleStrings : IEnumerable<string>
    {
        public IEnumerator<string> GetEnumerator()
        {
            Console.WriteLine("inside getenumerator");

            return null;//for testing purpose only
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }

Now if I replace the GetEnumerator as below when the same IEnumerator<string> name = sample.GetEnumerator(); is called then it does not step into the function. I would expect it to step into the function but not return anything since i did not call movenext() yet. What is happening when i specify yield that makes it behave this way. The program works.

public IEnumerator<string> GetEnumerator()
        {
            Console.WriteLine("inside getenumerator");
            yield return "First";
            yield return "Second";

        }
like image 972
ckv Avatar asked Dec 26 '22 23:12

ckv


1 Answers

The code of an IEnumerator containing yield is converted in two parts.

A new IEnumerator class is created, which contains your original code, albeit rewritten so that it executes as you'd expect. This is where your original code, include the Console.WriteLine lives.

The GetEnumerator() method contains brand-new generated code that simply instantiates the IEnumerator defined above and returns it.

As a consequence, none of your code runs before the first MoveNext() is called.

This is why you'd often see the following pattern, e.g. to perform arguments validation immediately. The implementation is split in two parts: a normal method and a method containing yield.

public IEnumerator<int> GetEnumerator(string whatever)
{
  // Perform validation immediately when called
  if (whatever == null) throw new ArgumentException();
  return GetEnumeratorInternal(whatever);
}

private IEnumerator<int> GetEnumeratorInternal(string whatever)
{
  // Everything in this method happens on first MoveNext
  yield return 1;
  yield return 2;
}
like image 55
jods Avatar answered Mar 15 '23 23:03

jods