In C# and .NET, as programmers we have access to an interface that is called IEnumerable (or IEnumerable for the generic version). Using IEnumerable allows us to iterate from a collection or data source by moving one element at a time. It’s also important to note that all collection types in C# inherit from IEnumerable so collections you are familiar with like arrays and lists implement IEnumerable. I have been trying to help educate around IEnumerable usage for many years now so this is a renewed effort to help get more junior developers understanding how they work.
As a bonus, if you’re interested in working with the code that you see in this article you can clone it down from GitHub by visiting this link.
A Companion Video
Simple IEnumerable Example
Let’s consider the following code example that will create an array of five integers. In order to prove that an array is assignable to an IEnumerable, let’s explicitly use the “as” operator so that we can tell the compiler that we want to do this conversion. If you’re in Visual Studio and you try this, you’ll notice that the Intellisense suggestions will tell you that the as IEnumerable<int>
code is actually unnecessary because it knows how to implicitly convert an array to an IEnumerable.
int[] myArray = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> myArrayAsEnumerable = myArray as IEnumerable<int>;
Console.WriteLine("Using foreach on the array...");
foreach (var item in myArray)
{
Console.WriteLine(item);
}
Console.WriteLine("Using foreach on the enumerable...");
foreach (var item in myArrayAsEnumerable)
{
Console.WriteLine(item);
}
Because IEnumerable allows us to iterate over a collection, it’s important to note that we can use a foreach loop but we cannot use a traditional counting for loop with a numeric indexer. This is because IEnumerable does not have an indexer property to retrieve elements from a particular spot in the collection.
Let’s have a look at the following code that demonstrates this:
Console.WriteLine("Using for loop on the array...");
for (int i = 0; i < myArray.Length; i++)
{
Console.WriteLine(myArray[i]);
}
// This will not work!
//for (int i = 0; i < myArrayAsEnumerable.Length; i++)
//{
// Console.WriteLine(myArrayAsEnumerable[i]);
//}
Are Lists Also IEnumerable?
Yes! As mentioned in the start of this article, all collection types in C# will implement this IEnumerable interface that allow us to iterate over them.
Here’s a quick example that demonstrates nearly the exact same as what we saw prior, but this time with a List (and a different set of numbers):
List<int> myList = new List<int> { 6, 7, 8, 9, 10 };
IEnumerable<int> myListAsEnumerable = myList as IEnumerable<int>;
Console.WriteLine("Using foreach on the list...");
foreach (var item in myList)
{
Console.WriteLine(item);
}
Console.WriteLine("Using foreach on the list enumerable...");
foreach (var item in myListAsEnumerable)
{
Console.WriteLine(item);
}
Feel free to try this out in your code editor with other collections examples from C#! Try something like a dictionary! How does the IEnumerable interface work for something like a dictionary when you try it in your own code?
IEnumerables Are Read-Only But….
An important note is that the IEnumerable interface itself is designed to give read-only access to the user of the data. This is because we can only iterate over it. However, let’s go make a small tweak to an earlier example from this article to see something interesting that you’ll want to keep in mind.
int[] myArray = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> myArrayAsEnumerable = myArray as IEnumerable<int>;
Console.WriteLine("Using foreach on the array...");
foreach (var item in myArray)
{
Console.WriteLine(item);
}
// Here is the change we're making!!
Console.WriteLine("Changing first item in the array to 123");
myArray[0] = 123;
Console.WriteLine("Using foreach on the enumerable...");
foreach (var item in myArrayAsEnumerable)
{
Console.WriteLine(item);
}
If you run the code above, what will happen? It looks like our second print out of numbers is modified to include the 123 at the start instead of 1!
So it’s true that the IEnumerable interface forces us to have read-only access. However, for newer programmers it’s a common mistake to assume that:
- The variable assignment of the array to another enumerable variable is a copy. It is in fact *not* a copy of the array, it is simply another variable that points to the same reference of the original array.
- Because IEnumerable gives read-only access, it can never change. This is also in fact false as demonstrated by the example above. Your access to the underlying data through your IEnumerable is read-only (unless you cast back to the collection), but this does not mean that someone else cannot be modifying the collection like we see in this example.
While these aren’t the most interesting aspects about enumerables, I wanted to make sure that they were called out under the assumption that you’re reading this as a more junior developer in C#.
IEnumerable Return Types For Functions
We can of course return an IEnumerable interface from functions just like we could any other collection. And there’s a special reason that I wanted to introduce this to you, the reader, towards the end of this article… But let’s keep going!
Let’s check a quick example of how we can return an Array of strings and a List of strings from a function when we mark the return type to be IEnumerable:
IEnumerable<string> FunctionThatReturnsAnArrayAsEnumerable()
{
return new string[] { "A", "B", "C" };
}
IEnumerable<string> FunctionThatReturnsAListAsEnumerable()
{
return new List<string> { "A", "B", "C" };
}
// we can use foreach
Console.WriteLine("Using foreach on the array function...");
foreach (var item in FunctionThatReturnsAnArrayAsEnumerable())
{
Console.WriteLine(item);
}
Console.WriteLine("Using foreach on the list function...");
foreach (var item in FunctionThatReturnsAListAsEnumerable())
{
Console.WriteLine(item);
}
The above example might not look too surprising based on some of the first code snippets we looked at and in this particular case, we are just down-casting the collections so that the callers of the functions have less access to the underlying collections. While there may be many reasons for wanting to do this, this opens up some doors for a more advanced topic where we can start to look at something called an “iterator” for C# and the enumerables that you are starting to use!
Summary
The IEnumerable interface allows us to have a read-only view of a source of data that only allows us to sequentially iterate over it. While this seems like it might be limiting compared to some of the characteristics we get even from Arrays and Lists, there are many reasons why writing code that works on enumerables can be very beneficial. This goal of this article was to provide a simple understanding for beginners on how enumerables operate and their relationship with other collections in C#.
Perhaps it’s time now to move onto Iterators!
Top comments (0)