What is yield and how does it work in C #?

The possibilities of C # are becoming wider from year to year. Various features make a programmer's life more pleasant, but the purpose and features of some of them may not be obvious to everyone. For example, the good old yield. For some developers, especially beginners, this is real magic - incomprehensible but interesting. This article will show you how yield actually works, and what is actually hidden behind this magic word. Enjoy reading!





Why yield is needed

yield . - , . , . , .





, , . , .





, . yield . – , , .





, yield , C#. , yield . , yield C#.





yield

, . , :





  • IEnumerable





  • IEnumerable<T>





  • IEnumerator





  • IEnumerator<T>





yield , . .





, yield, . , yield- , :





static IEnumerator GetInts()
{
  Console.WriteLine("first");
  yield return 1;

  Console.WriteLine("second");
  yield return 2;
}

static void Main()
{
  IEnumerator intsEnumerator = GetInts(); // print nothing
  Console.WriteLine("...");                    // print "..."

  intsEnumerator.MoveNext();                   // print "first"
  Console.WriteLine(intsEnumerator.Current);   // print 1
}
      
      



, GetInts , IEnumerator. . , .





MoveNext . yield return. , Current , yield return.





, "...", "first", 1 - , Current.





, MoveNext , , . , "second", Current 2.





, MoveNext , . , MoveNext false. false.





GetInts , , .





,

, yield-, MoveNext. :





IEnumerator GetNumbers()
{
  string stringToPrint = "moveNext";
  Console.WriteLine(stringToPrint);  // print "moveNext"
  yield return 0;
  Console.WriteLine(stringToPrint);  // print "moveNext"
  stringToPrint = "anotherStr";
  yield return 1;
  Console.WriteLine(stringToPrint);  // print "anotherStr"
}
      
      



GetNumbers MoveNext, "moveNext", - "anotherStr". .





. :





string message = "message1";

IEnumerator GetNumbers()
{
  Console.WriteLine(message);
  yield return 0;
  Console.WriteLine(message);
  yield return 1;
  Console.WriteLine(message);
}
void Method()
{
  var generator = GetNumbers();
  generator.MoveNext(); // print "message1"
  generator.MoveNext(); // print "message1"
  message = "message2";
  generator.MoveNext(); // print "message2"
}
      
      



GetNumbers , . , .





: , .





yield break

yield return yield break, , . MoveNext, yield break, false. , . , , yield, – -, '' yield break.





, yield break:





IEnumerator GenerateMultiplicationTable(int maxValue)
{
  for (int i = 2; i <= 10; i++)
  {
    for (int j = 2; j <= 10; j++)
    {
      int result = i * j;

      if (result > maxValue)
        yield break;

      yield return result;
    }
  }
}
      
      



2 10 . ( maxValue), . yield break.





IEnumerable

, , yield, IEnumerable, , . IEnumerable, , foreach.





. , IEnumerable, IEnumerable, IEnumerator. IEnumerator :). ? , .





:





void PrintFibonacci()
{
  Console.WriteLine("Fibonacci numbers:");

  foreach (int number in GetFibonacci(5))
  {
    Console.WriteLine(number);
  }
}

IEnumerable GetFibonacci(int maxValue)
{
  int previous = 0;
  int current = 1;

  while (current <= maxValue)
  {
    yield return current;

    int newCurrent = previous + current;
    previous = current;
    current = newCurrent;
  }
}
      
      



GetFibonacci , 1. , IEnumerable, foreach. PrintFibonacci.





, IEnumerable . , foreach GetEnumerator . GetEnumerator , . :





int _rangeStart;
int _rangeEnd;

void TestIEnumerableYield()
{
  IEnumerable polymorphRange = GetRange();

  _rangeStart = 0;
  _rangeEnd = 3;

  Console.WriteLine(string.Join(' ', polymorphRange)); // 0 1 2 3

  _rangeStart = 5;
  _rangeEnd = 7;

  Console.WriteLine(string.Join(' ', polymorphRange)); // 5 6 7
}

IEnumerable GetRange()
{
  for (int i = _rangeStart; i <= _rangeEnd; i++)
  {
    yield return i;
  }
}
      
      



string.Join IEnumerable, GetRange. , , foreach. _rangeStart _rangeEnd – IEnumerable !





LINQ, , , - , LINQ- . . , .





, , . , , , . .





yield

, yield . , , . Unity.





, yield, , – LINQ. yield , , , . , , :





public IEnumerable EnumerateAncestors(SyntaxNode node)
{
  while (node != null)
  { 
    node = node.Parent;
    yield return node;
  }
}
      
      



, . - , – , . , yield ( ), :).





yield , , , . , - , . :





  • , IEnumerator Reset, , yield-, . Reset NotSupportedException. : - , Reset;





  • yield -;





  • yield , unsafe-;





  • yield return try-catch. try try-finally. yield break try try-catch, try-finally .





- ?

, yield-, dotPeek. GetFibonacci, maxValue:





IEnumerable GetFibonacci(int maxValue)
{
  int previous = 0;
  int current = 1;

  while (current <= maxValue)
  {
    yield return current;

    int newCurrent = previous + current;
    previous = current;
    current = newCurrent;
  }
}

      
      



'Show compiler-generated code', dotPeek. GetFibonacci ?





, - :





[IteratorStateMachine(typeof(Program.d__1))]
private IEnumerable GetFibonacci(int maxValue)
{
  d__1 getFibonacciD1 = new d__1(-2);
  getFibonacciD1.<>4__this = this;
  getFibonacciD1.<>3__maxValue = maxValue;
  return (IEnumerable)getFibonacciD1;
}

      
      



, ? , . , .





(, IL):





[IteratorStateMachine(typeof(GetFibonacci_generator))]
private IEnumerable GetFibonacci(int maxValue)
{
  GetFibonacci_generator generator = new GetFibonacci_generator(-2);
  generator.forThis = this;
  generator.param_maxValue = maxValue;
  return generator;
}

      
      



, . . , , , C#-. . , , dotPeek ( – ildasm) :).





, , maxValue. '-2' – , , .





, , , . , .





:





class GetFibonacci_generator : IEnumerable,
                               IEnumerable,
                               IEnumerator,
                               IEnumerator,
                               IDisposable

      
      



, ... IDisposable! , , IEnumerator, GetFibonacci IEnumerable. , .





:





public GetFibonacci_generator(int startState)
{
  state = startState;
  initialThreadId = Environment.CurrentManagedThreadId;
}
      
      



, , '-2', . , , . , GetEnumerator:





IEnumerator IEnumerable.GetEnumerator()
{
  GetFibonacci_generator generator;

  if (state == -2 && initialThreadId == Environment.CurrentManagedThreadId)
  {
    state = 0;
    generator = this;
  }
  else
  {
    generator = new GetFibonacci_generator(0);
    generator.forThis = forThis;
  }

  generator.local_maxValue = param_maxValue;

  return generator;
}
      
      



, , . . :





IEnumerable enumerable = prog.GetFibonacci(5);
IEnumerator enumerator = enumerable.GetEnumerator();

Console.WriteLine(enumerable == enumerator);
      
      



, 'True'. ? :)





, GetEnumerator state '0'. , .





:





generator.local_maxValue = param_maxValue;
      
      



GetFibonacci (, , ), , param_maxValue . local_maxValue.





, maxValue 2 – param_maxValue local_maxValue. , , . MoveNext:





bool IEnumerator.MoveNext()
{
  switch (state)
  {
    case 0:
      state = -1;
      local_previous = 0;
      local_current = 1;
      break;
    case 1:
      state = -1;
      local_newCurrent = local_previous + local_current;
      local_previous = local_current;
      local_current = local_newCurrent;
      break;
    default:
      return false;
  }

  if (local_current > local_maxValue)
    return false;

  _current = local_current;
  state = 1;

  return true;
}
      
      



, GetFibonacci. MoveNext _current. Current .





( local_current > local_maxValue), '-1'. state – MoveNext - false.





, , MoveNext false, _current (, , Current) .





. state '-2'. , state = -2, MoveNext - false. , . , '-2' '0' GetEnumerator. MoveNext, GetEnumerator?





GetFibonacci – IEnumerable, , MoveNext . , , IEnumerable, IEnumerator, . MoveNext, GetEnumerator, ... false. , '' , .





. yield-, IEnumerable, , IEnumerable, IEnumerator. IEnumerator , , GetEnumerator. , , '', . :





IEnumerable enumerable = GetFibonacci(5);
IEnumerator deadEnumerator = (IEnumerator)enumerable;

for (int i = 0; i < 5; ++i)
{
  if (deadEnumerator.MoveNext())
  {
    Console.WriteLine(deadEnumerator.Current);
  }
  else
  {
    Console.WriteLine("Sorry, your enumerator is dead :(");
  }
}

IEnumerator enumerator = enumerable.GetEnumerator();
Console.WriteLine(deadEnumerator == enumerator);

for (int i = 0; i < 5; ++i)
{
  if (deadEnumerator.MoveNext())
  {
    Console.WriteLine(deadEnumerator.Current);
  }
  else
  {
    Console.WriteLine("Sorry, your enumerator is dead :(");
  }
}
      
      



, ? : 5 1, 1, 2, 3, 5.





IEnumerator. IEnumerable?





, GetEnumerator, , IEnumerable . , :





IEnumerable enumerable = GetInts(0);                     
IEnumerator firstEnumerator = enumerable.GetEnumerator();
IEnumerable firstConverted = (IEnumerable)firstEnumerator;

Console.WriteLine(enumerable == firstEnumerator);
Console.WriteLine(firstConverted == firstEnumerator);
Console.WriteLine(firstConverted == enumerable);
      
      



'True' , . , , ( - ) .





, IEnumerable GetEnumerator ( , )? , yield-:





IEnumerable RepeatLowerString(string someString)
{
  someString.ToLower();

  while (true)
  {
    yield return someString;
  }
}
      
      



, . .





, ? RepeatLowerString, , , . ?





, ToLower , - , . , , , . ToLower, , , - , "" - .





- . , . , RepeatLowerString, . , " " – , .





, . PVS-Studio. , , C#, C, C++ Java. , PVS-Studio .





, , RepeatLowerString:





IEnumerable RepeatLowerString(string someString)
{
  string lower = someString.ToLower();

  while (true)
  {
    yield return lower;
  }
}
      
      



, IEnumerable:





IEnumerable enumerable = RepeatLowerString("MyString");
IEnumerator firstEnumerator = enumerable.GetEnumerator();

IEnumerator secondEnumerator = enumerable.GetEnumerator();
var secondConverted = (IEnumerable)secondEnumerator;

var magicEnumerator = secondConverted.GetEnumerator();

for (int i = 0; i < 5; i++)
{
  magicEnumerator.MoveNext();
  Console.WriteLine(magicEnumerator.Current);
}
      
      



?





! , NullReferenceException. ?





, . , . , .





, magicEnumerator.MoveNext() ToLower. someString, param_someString local_someString:





public string param_someString;
private string local_someString;
      
      



, , , , MoveNext, , local_someString:





bool IEnumerator.MoveNext()
{
  switch (this.state)
  {
    case 0:
      this.state = -1;
      this.local_lower = this.local_someString.ToLower();
      break;
    case 1:
      this.state = -1;
      break;
    default:
      return false;
  }
  this._current = this.local_lower;
  this.state = 1;
  return true;
}
      
      



, null . ?





GetEnumerator local_someString param_someString:





IEnumerator IEnumerable.GetEnumerator()
{
  RepeatLowerString_generator generator;

  if (state == -2 && initialThreadId == Environment.CurrentManagedThreadId)
  {
    state = 0;
    generator = this;
  }
  else
  {
    generator = new RepeatLowerString_generator(0);
    generator.forThis = forThis;
  }

  generator.local_someString = param_someString;

  return generator;
}
      
      



, null ? . null? - :





IEnumerable enumerable = RepeatLowerString("MyString");
IEnumerator firstEnumerator = enumerable.GetEnumerator();

IEnumerator secondEnumerator = enumerable.GetEnumerator();
var secondConverted = (IEnumerable)secondEnumerator;

var magicEnumerator = secondConverted.GetEnumerator();

for (int i = 0; i < 5; i++)
{
  magicEnumerator.MoveNext(); // NRE
  Console.WriteLine(magicEnumerator.Current);
}
      
      



GetEnumerator , local_SomeString . param_someString? , – GetEnumerator . , – , null.





param_someString local_someString magicEnumerator! local_someString.ToLower().





. GetEnumerator this, IEnumerable. , param_*. yield-, - . :





IEnumerable GetPositive()
{
  int i = 0;

  while (true)
    yield return ++i;
}
      
      



, 1. :





IEnumerable enumerable = GetPositive();
IEnumerator firstEnumerator = enumerable.GetEnumerator();

IEnumerator secondEnumerator = enumerable.GetEnumerator();
var secondConverted = (IEnumerable)secondEnumerator;

IEnumerator magicEnumerator = secondConverted.GetEnumerator();

for (int i = 0; i < 5; i++)
{
  magicEnumerator.MoveNext();
  Console.WriteLine(magicEnumerator.Current);
}
      
      



1 5. , :).





2

– , . , , , .





yield-:





IEnumerable GetInts(int i)
{
  while (true)
  {
    yield return i++;
  }
}
      
      



, , i. MoveNext :





bool IEnumerator.MoveNext()
{
  switch (this.state)
  {
    case 0:
      this.state = -1;
      break;
    case 1:
      this.state = -1;
      break;
    default:
      return false;
  }
  this._current = this.local_i++;
  this.state = 1;
  return true;
}
      
      



, , local_i, MoveNext. , GetEnumerator – – , param_i:





IEnumerator IEnumerable.GetEnumerator()
{
  GetInts_generator generator;

  if (   state == -2 
      && initialThreadId == Environment.CurrentManagedThreadId)
  {
    state = 0;
    generator = this;
  }
  else
  {
    generator = new GetInts_generator(0);
    generator.forThis = forThis;
  }

  generator.local_i = param_i;

  return generator;
}
      
      



param_i, , yield- GetInts:





[IteratorStateMachine(typeof(GetInts_generator))]
private IEnumerable GetInts(int i)
{
  GetInts_generator generator = new GetInts_generator(-2);
  generator.forThis = this;
  generator.param_i = i;
  return generator;
}
      
      



. - param_i? , , local_i?





yield- GetInts – IEnumerable. GetEnumerator. , . , :





IEnumerable enumerable = GetInts(0);
// enumerable.param_i = 0

IEnumerator firstEnumerator = enumerable.GetEnumerator(); 
// firstEnumerator.local_i = enumerable.param_i

Console.WriteLine(enumerable == firstEnumerator); // True

firstEnumerator.MoveNext(); 
// firstEnumerator.local_i++
firstEnumerator.MoveNext(); 
// firstEnumerator.local_i++

IEnumerator secondEnumerator = enumerable.GetEnumerator(); 
// secondEnumerator.local_i = ?

      
      



GetInts, -. param_i – '0'. firstEnumerator. , , enumerable. , GetEnumerator local_i param_i enumerable.





MoveNext. local_i, firstEnumerator, enumerable, .





IEnumerator. , local_i? , , yield- .





param_i. , local_i MoveNext, param_i . , local_i , GetEnumerator.





. , GetEnumerator, . , , yield-. .





IEnumerator

, yield-, IEnumerable. , IEnumerator, IEnumerable. , , IEnumerator.





, IEnumerable. , . , yield-, IEnumerator yield-, IEnumerable:





  • GetEnumerator;





  • initialThreadId;





  • .





, . , , yield-, IEnumerable, state '-2' GetEnumerator. state MoveNext false - .





, IEnumerator, GetEnumerator . '0' state .





Dispose

Dispose - , IEnumerable<T> IDisposable. Dispose . , Dispose . using.





:





using (var disposableVar = CreateDisposableObject())
{
  ....
}

      
      



using var disposableVar = CreateDisposableObject();
....

      
      



Dispose disposableVar ( ), ( ). using .





using yield- . , , using, Dispose. , , , Dispose , .





, Dispose Dispose . , , , using yield-.





:





static IEnumerable GetLines(string path)
{
  using (var reader = new StreamReader(path))
  {
    while (!reader.EndOfStream)
      yield return reader.ReadLine();
  }
}
      
      



, . using GetEnumerator, :





private void Finally1()
{
  this.state = -1;
  if (this.local_reader == null)
    return;
  this.local_reader.Dispose();
}
      
      



, Dispose state , MoveNext ( ) - false.





finally- – using MoveNext Dispose. Dispose :





void IDisposable.Dispose()
{
  switch (this.state)
  {
    case -3:
    case 1:
      try
      {
      }
      finally
      {
        this.Finally1();
      }
      break;
  }
}

      
      



, using (, , ). , :).





Dispose , . , , IDisposable . , , , .





, MoveNext:





bool IEnumerator.MoveNext()
{
  try
  {
    switch (this.state)
    {
      case 0:
        this.state = -1;
        this.local_reader = new StreamReader(this.local_path);
        this.state = -3;
        break;
      case 1:
        this.state = -3;
        break;
      default:
        return false;
    }
    if (!this.local_reader.EndOfStream)
    {
      this._current = this.local_reader.ReadLine();
      this.state = 1;
      return true;
    }
    this.Finally1();
    this.local_reader = null;
    return false;
  }
  fault
  {
    Dispose();
  }
}

      
      



, using yield-. fault. C# , IL-. : try , , fault. , , ! ? fault :).





, , Dispose , using, , . .





Reset!

, Reset :





[DebuggerHidden]
void IEnumerator.Reset()
{
  throw new NotSupportedException();
}

      
      



, – NotSupportedException. , , , Reset. , .





, yield C#. – , , yield , - ' ', .





'' , yield return try-catch. , , yield-, . , - , .





, yield, . , , , , , , LINQ. , , ' ', , - List :).





, Twitter – , , .





, . !





, : Nikita Lipilin. What Is yield and How Does It Work in C#?.








All Articles