Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

IEnumerable<T> with yield return — iterators

IEnumerable<T> Name(<params>) { … yield return <item>; … yield break; … }

A function declared `IEnumerable<T>` whose body uses `yield return` is a C# iterator: each `yield return` adds one item to the sequence it hands back, and `yield break` ends it early. The caller gets the whole sequence — `foreach`, LINQ, `.Count()` and `.ToList()` all work on it.

stable3 examples compiled by CIfunctionconstructcsharp

Summary#

Write the C# iterator you would write anywhere else:

IEnumerable<int> Evens(int upTo) {
  for (int i = 0; i <= upTo; i += 2) {
    yield return i;
  }
}

[Test]
void An_Iterator_Hands_Back_Its_Sequence() {
  var seen = new List<int>();
  foreach (var n in Evens(6)) seen.Add(n);
  Assert.Equal(4, seen.Count);
  Assert.Equal(12, Evens(6).Sum());
  Assert.Equal("4,6", string.Join(",", Evens(6).Where(n => n > 2)));
}

Signature#

IEnumerable<T> Name(<params>) {
  …
  yield return <item>;     // add one item to the sequence
  …
  yield break;             // end the sequence here
}

Description#

How do I end the sequence early?#

yield break; ends it with what was produced so far — the same as reaching the end of the body:

IEnumerable<int> UpTo(int n) {
  for (int i = 0; ; i++) {
    if (i >= n) yield break;
    yield return i;
  }
}

[Test]
void Yield_Break_Ends_The_Sequence() {
  Assert.Equal(3, UpTo(3).Count());
}

What does the caller hold?#

The whole sequence, as Osy#'s read-only sequence type — so var xs = Evens(10); is an int[] and everything an array does works on it. A parameter or local may say it holds one as IEnumerable<int> too — the collection interfaces are the collections they name (IEnumerable<T>, IReadOnlyList<T>, IList<T> — the collection interfaces).

When is the sequence produced?#

When the iterator is CALLED, all at once — C# produces it one item per step of the caller's loop instead. The answer is the same for every iterator that ends; what differs is only WHEN its body runs relative to its reader's.

An iterator that never ends#

An iterator whose loop can never end is meant to be cut short by its reader, and it is — the reader says how many items it needs, and the iterator stops there:

IEnumerable<int> Naturals() {
  int i = 0;
  while (true) { yield return i++; }
}

[Test]
void An_Endless_Iterator_Is_Cut_Short_By_Its_Reader() {
  Assert.Equal(10, Naturals().Take(5).Sum());
  Assert.Equal(12, Naturals().Where(x => x % 3 == 0).Select(x => x * 2).First(y => y > 10));
  Assert.Equal(4, Naturals().TakeWhile(x => x < 4).Count());
  Assert.True(Naturals().Contains(42));
}

The readers that can say how much they need are .Take(n) (also .Skip(k).Take(n)), .First(), .FirstOrDefault() and .Any() — each with or without a predicate — .ElementAt(i), .TakeWhile(…) and .Contains(x), with any .Where(…) and .Select(…) between the call and the reader. Any other reader of an endless iterator — a foreach, a .ToList(), a .Count() — would never finish, in C# as here, and is a compile error that names the readers above. A foreach that breaks out of an endless iterator is one of those: write the stopping point as the reader (foreach (var n in Naturals().TakeWhile(x => x < 100))).

The iterator METHOD of a class keeps the older rule — its loop needs a way out — because its call sites cannot yet hand it what its reader needs; declare it as a top-level function to read it this way.

A sequence whose items arrive over TIME — a long query, a model's streamed answer — is a different thing with its own declaration: yield — a function that produces results over time (stream<T>), which a page watches as it fills.

See also#

Related

yield — a function that produces results over time

A `stream<T>` function produces its results one at a time instead of all at once, and a `live var` bound to one renders…

foreach

Walks a collection — a query result, a list, or a parent's children. The normal way to iterate; reach for a for loop…

LINQ over a local list

Query a local `List<T>`, `HashSet<T>` or `T[]` — of your own `class` values OR of plain scalars like `string[]` and…

function

A function is a top-level unit of work, written like a C# method — a return type, a name, typed parameters, a body. It…