HomeSearch

C# Yield Return (Generate IEnumerable in foreach)

This C# article uses the yield keyword to implement IEnumerable. Yield makes foreach-loops more powerful.

Yield.

This keyword interacts with the foreach-loop. It is a contextual keyword: yield is a keyword only in certain statements. It helps with looping.Keywords

Yield, keyword notes.

Yield allows each iteration in a foreach-loop be generated only when needed. In this way it can improve performance.

Example.

We use yield in methods that return the type IEnumerable (without any angle brackets), or the type IEnumerable<Type> with a type parameter in the angle brackets.

Info: We reference the System.Collections namespace for the first version, and the System.Collections.Generic namespace for the second.

Main: In the part of the foreach-loop following the "in" keyword, there is a method call to ComputePower.

Foreach

ComputePower: This receives 2 parameters. This method returns an IEnumerable<int> type, which we can use in a foreach-loop.

C# program that uses foreach, yield return using System; using System.Collections.Generic; public class Program { static void Main() { // // Compute two with the exponent of 30. // foreach (int value in ComputePower(2, 30)) { Console.Write(value); Console.Write(" "); } Console.WriteLine(); } public static IEnumerable<int> ComputePower(int number, int exponent) { int exponentNum = 0; int numberResult = 1; // // Continue loop until the exponent count is reached. // while (exponentNum < exponent) { // // Multiply the result. // numberResult *= number; exponentNum++; // // Return the result with yield. // yield return numberResult; } } }

Notes, IEnumerable.

IEnumerable is an interface. It describes a type that implements the internal methods required for using foreach-loops.IEnumerable

Return.

Yield return is similar to a return statement (which passes control flow to the calling method), followed by a "goto" to the yield statement in the next iteration of the foreach.ReturnGoto

Tip: This behavior does not exist in the Common Language Runtime. It is implemented by a class generated by the C# compiler.

Then: This is executed and JIT-compiled by the CLR. Yield is a form of syntactic sugar.

Some research.

Microsoft provides a method that is essentially equivalent. But the implementation uses the non-generic IEnumerable type. This introduces some inefficiencies.

Note: It likely results in the integers being boxed, because IEnumerable does not have knowledge of their type and instead acts on Object.

Yield: Microsoft Docs

Internals.

The C# code you have that uses yield is never actually executed by the CLR at runtime. Instead, the C# compiler transforms that code before the runtime ever occurs.

Tip: The compiler-generated class, marked with CompilerGenerated, instead uses several integral types.

Result: We see an entire class that is similar to how your code would look if you manually implemented the interfaces.

Compiler-generated class: C# // Nested Types [CompilerGenerated] private sealed class <ComputePower>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int> // ... { // Fields private int <>1__state; private int <>2__current; public int <>3__exponent; public int <>3__number; private int <>l__initialThreadId; public int <exponentNum>5__1; public int <numberResult>5__2; public int exponent; public int number; // Methods [omitted] }

Notes, internals.

The actual class implements several more interfaces. The punctuation characters allow the compiler to ensure no naming conflicts will occur with your code.

Performance.

Is yield return fast? Or does it incur a significant performance loss? Yield return does have some overhead, but if you need its advanced features it can be faster.

Here: We test two methods, one that uses a simple multiply and the other that uses yield return to do the same thing.

Result: The version that uses yield return runs many times slower. For simple things, avoid yield return for top speed.

C# program that benchmarks yield return using System; using System.Collections.Generic; using System.Diagnostics; class Program { const int _max = 1000000; static void Main() { int[] numbers = { 10, 20, 30, 40, 50 }; var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { if (Method1(numbers) != 300) { throw new Exception(); } } s1.Stop(); var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { if (Method2(numbers) != 300) { throw new Exception(); } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } static int Method1(int[] numbers) { // Sum with simple statements. int sum = 0; foreach (int number in numbers) { sum += number * 2; } return sum; } static int Method2(int[] numbers) { // Use yield return to get multiplied numbers and then sum those. int sum = 0; foreach (int number in GetNumbers(numbers)) { sum += number; } return sum; } static IEnumerable<int> GetNumbers(int[] numbers) { // Return all numbers multiplied. foreach (int number in numbers) { yield return number * 2; } } } Output 3.84 ns inline expression 50.68 ns yield return

Yield benefits.

Suppose we have a large (or even infinite) set of items (like prime numbers, or species of beetles). Yield can provide a benefit here.

Tip: We can just loop over items from the infinite set until we encounter one that matches our requirements.

And: This reduces memory, reduces loading time of the set, and can also result in cleaner C# code.

A summary.

We examined the yield return pattern. The yield keyword is contextual—it only has special significance in some places. It helps simplify foreach-loops.
Home
Dot Net Perls
© 2007-2019 Sam Allen. All rights reserved. Written by Sam Allen, info@dotnetperls.com.