In C# and .NET, as programmers we have access to an interface that is called IEnumerable (or IEnumerable<T> 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 to understand 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 on CSharp IEnumerable
As you read through this article on C# IEnumerables, you may find this video valuable!
Simple CSharp 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 a CSharp 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 in CSharp?
Yes! As mentioned at 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?
CSharp 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#.
CSharp 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!
Summarizing IEnumerable in C#
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! Remember to check out my courses! If you're interested in more learning opportunities, subscribe to my free weekly newsletter and check out my YouTube channel!
Affiliations
These are products & services that I trust, use, and love. I get a kickback if you decide to use my links. There’s no pressure, but I only promote things that I like to use!
- BrandGhost: My social media content and scheduling tool that I use for ALL of my content!
- RackNerd: Cheap VPS hosting options that I love for low-resource usage!
- Contabo: Affordable VPS hosting options!
- ConvertKit: The platform I use for my newsletter!
- SparkLoop: Helps add value to my newsletter!
- Opus Clip: Tool for creating short-form videos!
- Newegg: For all sorts of computer components!
- Bulk Supplements: Huge selection of health supplements!
- Quora: I answer questions when folks request them!