IEnumerable
In C# IEnumerable
things (like arrays or Lists) have elements that come one after another. They can be looped over with foreach
.
Besides looping over IEnumerable
things, we can invoke extension methods upon them. System.Linq
gives us many methods that act upon IEnumerables
.
Here we use IEnumerable
as the return value of a query expression. We use it with foreach
-loops, and also call extension methods on it.
IEnumerable
is returned from a query expression. A query that selects ints will be of type IEnumerable
int
.IEnumerable
variable, we can use the foreach
-loop. This loop iterates with simple syntax.using System; using System.Collections.Generic; using System.Linq; // Part 1: query expression. IEnumerable<int> result = from value in Enumerable.Range(0, 5) select value; // Part 2: loop over IEnumerable. foreach (int value in result) { Console.WriteLine($"IENUMERABLE: {value}"); }IENUMERABLE: 0 IENUMERABLE: 1 IENUMERABLE: 2 IENUMERABLE: 3 IENUMERABLE: 4
In System.Linq
, many extension methods that act upon IEnumerable
are available. These are used in many C# programs.
IEnumerable
(like an int
array).IEnumerable
instance, including the ToList
and ToArray
conversions.using System; using System.Collections.Generic; using System.Linq; int[] values = new int[] { 2, 3, 1 }; // Part 1: use Average extension method on IEnumerable. double average = values.Average(); Console.WriteLine($"AVERAGE: {average}"); // Part 2: convert IEnumerable with ToList extension. List<int> list = values.ToList(); Console.WriteLine($"TOLIST: {list.Count}");AVERAGE: 2 TOLIST: 3
Many classes implement IEnumerable
. We can pass them directly to methods that receive IEnumerable
arguments. The type parameter must be the same.
Display()
receives an IEnumerable
argument. We can pass Lists or arrays to it.IEnumerable
on a type to provide support for the foreach
-loop. This is done through the GetEnumerator
method.using System; using System.Collections.Generic; class Program { static void Main() { Display(new List<bool> { true, false, true }); } static void Display(IEnumerable<bool> argument) { foreach (bool value in argument) { Console.WriteLine(value); } } }True False True
IEnumerable
works with a 2D array. It enables a foreach
-loop over the values in a 2D or jagged array. We create a custom method that returns IEnumerable
.
IEnumerable
and the foreach
-loop to access, in sequence, all items in a 2D array. We can abstract
the loop itself out.IEnumerable
T.IEnumerable
collection of ints. We must specify the int
in angle brackets.using System; using System.Collections.Generic; class Program { static int[,] _grid = new int[15, 15]; static void Main() { // Initialize some elements in 2D array. _grid[0, 1] = 4; _grid[4, 4] = 5; _grid[14, 2] = 3; // Sum values in 2D array. int sum = 0; foreach (int value in GridValues()) { sum += value; } // Write result. Console.WriteLine("SUMMED 2D ELEMENTS: " + sum); } public static IEnumerable<int> GridValues() { // Use yield return to return all 2D array elements. for (int x = 0; x < 15; x++) { for (int y = 0; y < 15; y++) { yield return _grid[x, y]; } } } }SUMMED 2D ELEMENTS: 12
GetEnumerator
errorIn a class
, the foreach
-loop is not by default supported. A GetEnumerator
method (often part of the IEnumerable
interface
) is required.
IEnumerable
to fix this error on a class
. This provides the GetEnumerator
method.using System; class Example { } class Program { static void Main() { Example example = new Example(); // This does not compile: GetEnumerator is required. foreach (string element in example) { Console.WriteLine(true); } } }error CS1579: foreach statement cannot operate on variables of type 'Example' because 'Example' does not contain a public definition for 'GetEnumerator'
IEnumerable
This example implements the IEnumerable
interface
on an Example class
. The class
contains a List
, and for GetEnumerator
, we use the List
's GetEnumerator
method.
List
. Our IEnumerable
implementation relies on another.class
constructor, which populates the _elements field.foreach
-loop in the Main
method implicitly (secretly) calls the GetEnumerator
method. So "HERE" is written.foreach
loop refers to the List
's Enumerator and loops over the elements of the List
.using System; using System.Collections; using System.Collections.Generic; class Example : IEnumerable<string> { List<string> _elements; public Example(string[] array) { this._elements = new List<string>(array); } IEnumerator<string> IEnumerable<string>.GetEnumerator() { Console.WriteLine("HERE"); return this._elements.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this._elements.GetEnumerator(); } } class Program { static void Main() { Example example = new Example(new string[] { "cat", "dog", "bird" }); // The foreach-loop calls the generic GetEnumerator method. // ... It then uses the List's Enumerator. foreach (string element in example) { Console.WriteLine(element); } } }HERE cat dog bird
Some methods, like Enumerable.Range()
, make it easier to create IEnumerable
collections. We do not need to create a separate array.
Enumerable.Range
, which returns an IEnumerable
collection. We enumerate its return value with foreach
.Enumerable.Repeat
and Empty, are available. They can be useful in certain programs.using System; using System.Linq; class Program { static void Main() { // Get IEnumerable from Enumerable.Range and loop over it. foreach (int value in Enumerable.Range(100, 2)) { Console.WriteLine("RANGE(0, 2): {0}", value); } } }RANGE(0, 2): 100 RANGE(0, 2): 101
IEnumerable
A single IEnumerable
method can reduce code size—this has speed advantages. For numeric methods, though, using an array directly is usually a faster approach.
int
array with for each, and sum them. No IEnumerable
is involved.AsEnumerable
to get an IEnumerable
of the int
array. Then we use foreach
over the IEnumerable
.IEnumerable
foreach
-loop is much slower. Prefer foreach
for this type of code.using System; using System.Diagnostics; using System.Linq; const int _max = 1000000; int[] values = { 10, 20, 30 }; // Version 1: loop over array directly. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { int sum = 0; foreach (int value in values) { sum += value; } if (sum == 0) { return; } } s1.Stop(); // Version 2: convert array to IEnumerable and loop over IEnumerable elements. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { int sum = 0; var enumerable = values.AsEnumerable(); foreach (int value in enumerable) { sum += value; } if (sum == 0) { return; } } 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")); 2.53 ns int[], foreach 38.80 ns IEnumerable int, foreach
This keyword is placed before "return." It is used in methods that return IEnumerable
. We can use yield to "save the state" of the function after the return.
This kind of loop has advantages. It results in simpler, clearer syntax. We no longer need to track indexes with variables (which often have confusing names).
IOrderedEnumerable
If we use an orderby
clause in a query expression, we receive an IOrderedEnumerable
. This can be used in the same way as an IEnumerable
—we can loop over it.
This generic interface
provides an abstraction for looping over elements. It provides foreach
-loop support. And it allows us to use LINQ extensions.