Programs spend most of their time in loops. In C#, we find the foreach
-loop, which has special features, and clearer syntax.
With this loop, we evaluate each element individually—an index is not needed. With no indexes, loops are simpler. This makes programs easier to read and understand.
foreach
-loopForeach accesses each element in order—this is called enumeration. We eliminate errors caused by incorrect index handling.
string
array with 3 elements—each the name of a common pet.foreach
to loop through the elements in the array. Each element can be accessed with the identifier ("value") we choose.string
element. Foreach returns each element in order.using System; // Step 1: create an array of 3 strings. string[] pets = { "dog", "cat", "bird" }; // Step 2: loop with the foreach keyword. foreach (string value in pets) { // Step 3: access the enumeration variable. Console.WriteLine("FOREACH ITEM: " + value); }FOREACH ITEM: dog FOREACH ITEM: cat FOREACH ITEM: bird
Reverse
To use a reverse foreach
-loop, we can call Reverse()
. We can then use foreach
over the reversed results. Reverse()
is an extension method—make sure to include System.Linq
.
Reverse()
receives an IEnumerable
collection. IEnumerable
is implemented by things like arrays and Lists.using System; using System.Linq; // An example array. string[] array = { "bird", "frog", "dog", "snake" }; // Use the Reverse generic extension method. foreach (string value in array.Reverse()) { Console.WriteLine("REVERSE VALUE: " + value); }REVERSE VALUE: snake REVERSE VALUE: dog REVERSE VALUE: frog REVERSE VALUE: bird
This example uses a foreach
-loop to evaluate a LINQ expression. The expression sorts an array. Foreach causes a query to be evaluated.
orderby
to sort the strings. This expression is not evaluated yet—it is lazy.foreach
, we evaluate the lazy query expression from step 2, and print each string
. They are sorted alphabetically.using System; using System.Linq; // Step 1: an unsorted string array. string[] letters = { "d", "c", "a", "b" }; // Step 2: use query to sort the array alphabetically. var sorted = from letter in letters orderby letter select letter; // Step 3: loop with the foreach keyword. foreach (string value in sorted) { Console.WriteLine(value); }a b c d
This feature allows the position in the control flow to be remembered and resumed when the statement finishes. It is implemented in terms of simpler constructs.
X()
method in the foreach
-loop in Main
. Inside X()
, an infinite sequence of numbers is returned by yield return.X()
is called again.using System; using System.Collections.Generic; public class Program { static void Main() { foreach (int value in X()) { Console.WriteLine(value); // Break at value 10. if (value == 10) { break; } } } static IEnumerable<int> X() { // Return infinite numbers. int i = 0; while (true) { i++; yield return i; } } }1 2 3 4 5 6 7 8 9 10
The var
keyword can simplify our loop syntax. The example enumerates a Dictionary
. The character count in "KeyValuePair
" can be reduced.
foreach
-loop uses var
in its opening statement. The variable (var
) is of type KeyValuePair
(int
, int
).using System; using System.Collections.Generic; // Add items to Dictionary. var data = new Dictionary<int, int>(); data.Add(1, 2); data.Add(2, 3); data.Add(3, 4); // Use var in foreach-loop. foreach (var pair in data) { Console.WriteLine("KEY, VALUE: {0}, {1}", pair.Key, pair.Value); }KEY, VALUE: 1, 2 KEY, VALUE: 2, 3 KEY, VALUE: 3, 4
List
exceptionThe foreach
loop has a drawback. It restricts any mutations made to the collection during the loop. This can impact code that uses Lists.
foreach
-loop over each element in the List
. We then try calling the Remove()
method.Remove()
call fails because of a restriction of the foreach
-loop. To add or remove elements inside a loop, consider a for
-loop.using System; using System.Collections.Generic; List<int> list = new List<int>(); list.Add(1); list.Add(2); list.Add(3); // Loop over list elements using foreach-loop. foreach (int element in list) { Console.WriteLine(element); } // You can't remove elements in a foreach-loop. try { foreach (int element in list) { list.Remove(element); } } catch (Exception ex) { Console.WriteLine(ex.Message); }1 2 3 Collection was modified; enumeration operation may not execute.
Let's compare loops. For has more complexity in its syntax. This gives it more power if you want to modify the collection or examine adjacent elements.
using System; // Array of color strings. string[] colors = { "red", "orange", "yellow", "green" }; // Print all colors with for-loop. Console.WriteLine(":::FOR:::"); for (int i = 0; i < colors.Length; i++) { // Assign string reference based on induction variable. string value = colors[i]; Console.WriteLine(value); } // Print all colors with foreach. Console.WriteLine(":::FOREACH:::"); foreach (var value in colors) { Console.WriteLine(value); }:::FOR::: red orange yellow green :::FOREACH::: red orange yellow green
We cannot assign the variable we use in a foreach
-loop. To fix this error, we can add another local inside the loop—that local can be reassigned as much as we want.
for
-loop is often a better choice. With for, we can directly assign elements.class Program { static void Main() { foreach (int value in new int[] { 1, 2, 3 }) { value = 20; } } }Error CS1656 Cannot assign to 'value' because it is a 'foreach iteration variable'
We cannot use a foreach
statement on many kinds of variables such as tuples. An implementation of GetEnumerator
(or IEnumerable
) is always required.
using System; class Program { static void Main() { var tuple = new Tuple<int, int>(0, 0); // Cannot use foreach on tuple. foreach (int value in tuple) { } } }Error CS1579 foreach statement cannot operate on variables of type 'Tuple<int, int>' because 'Tuple<int, int>' does not contain a public instance definition for 'GetEnumerator'
When we specify the variable type in foreach
, there must exist a conversion to it. Here the string
"test" returns chars.
char
cannot be converted to a string
implicitly (with no cast) so we get an error.string
keyword in "foreach
" to char
(or int
, which has a conversion from a char
).class Program { static void Main() { string test = "bird"; // The foreach variable must have a conversion. foreach (string value in test) { } } }Error CS0030 Cannot convert type 'char' to 'string'
How can we improve the iteration performance of a foreach
-loop? The foreach
-loop is often less efficient than a simple for
-loop.
foreach
-loop that directly accesses the instance field _values.foreach
-loop.for
-loop and foreach
have the same performance in 2021 on .NET 5 running on Linux. We should just choose the clearest loop.using System; using System.Diagnostics; class Program { int[] _values = { 1, 2, 3 }; int Method1() { // Access the field directly in the foreach expression. int result = 0; foreach (int value in this._values) { result += value; } return result; } int Method2() { // Store the field into a local variable and then iterate. int result = 0; var values = this._values; foreach (int value in values) { result += value; } return result; } const int _max = 100000000; static void Main() { Program program = new Program(); // Version 1: use foreach-loop on field. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { program.Method1(); program.Method1(); } s1.Stop(); // Version 2: use foreach-loop on local. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { program.Method2(); program.Method2(); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000 * 1000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000 * 1000) / _max).ToString("0.00 ns")); } }6.77 ns: foreach, field 6.72 ns: foreach, local variable
Foreach is a powerful and elegant loop. It uses the "in" keyword and reads like a sentence—and reduces possible bugs. It handles arrays and works well with queries.