The C# yield
-keyword interacts with the foreach
-loop. It is a contextual keyword: yield is a keyword only in certain statements. It can make loops clearer and simpler to write.
Yield allows each iteration in a foreach
-loop to be generated only when needed. In this way (by enabling lazy evaluation) it can improve performance.
We use the yield
-keyword in methods that return the type IEnumerable
(without any angle brackets), or the type IEnumerable
with a type parameter in the angle brackets.
System.Collections
namespace for the first version, and the System.Collections.Generic
namespace for the second.foreach
-loop following the in
-keyword, there is a method call to ComputePower
.ComputePower()
receives 2 parameters. It returns an IEnumerable
int
type, which we can use in a foreach
-loop.using System; using System.Collections.Generic; public class Program { static void Main() { // Part 1: compute powers of two with the exponent of 30. foreach (int value in ComputePower(2, 30)) { Console.WriteLine(value); } } public static IEnumerable<int> ComputePower(int number, int exponent) { // Part 2: continue loop until the exponent count is reached. // ... Yield the current result. int exponentNum = 0; int numberResult = 1; while (exponentNum < exponent) { // Multiply the result. numberResult *= number; exponentNum++; // Return the result with yield. yield return numberResult; } } }2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 1073741824
The C# code you have that uses yield is not directly executed by the runtime. Instead, the C# compiler transforms that code before the runtime ever occurs.
class
, marked with CompilerGenerated
, instead uses several integral types.class
that is similar to how your code would look if you manually implemented the interfaces.// 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] }
Is yield-return
fast? Or does it incur a significant performance loss? This code does have some overhead, but if you need its advanced features it can be faster.
foreach
-loop over an array and multiplies each element.yield-return
method (one that returns IEnumerable
) to perform the multiplication.yield-return
runs many times slower. For simple things, avoid yield-return
for top speed.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(); // Version 1: use foreach with array. for (int i = 0; i < _max; i++) { if (Method1(numbers) != 300) { throw new Exception(); } } s1.Stop(); var s2 = Stopwatch.StartNew(); // Version 2: use foreach with yield keyword. 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; } } } 3.84 ns inline expression 50.68 ns yield return
IEnumerable
IEnumerable
is an interface
. It describes a type that implements the internal methods required for using foreach
-loops.
Yield return is similar to a return statement, followed by a "goto
" to the yield
-statement in the next iteration of the foreach
-loop.
Suppose we have a large (or even infinite) set of items (like prime numbers, or species of beetles). Yield can provide a benefit here.
The yield
-keyword is contextual—it only has special significance in some places. Often, the yield return pattern helps simplify foreach
-loops.