Introduction
Imagine you are trying to store and manage a sequence of elements in an application. You will use any of the data structures available in the System.Collection namespace to store and retrieve elements as per the requirements and expected behavior.
To traverse through the elements in any collection, you will generally use the foreach looping structure provided in the language. The looping variable assigned will contain the current element of the collection being iterated over.
IEnumerator, IEnumerable and IQueryable are three interfaces used in storing, retrieving and querying over a collection of data.
This article details about each of these interfaces in detail and summarizes with 5 differences between them.
What is an IEnumerator?
IEnumerator is an interface from Systems.Collection namespace which provides the infrastructure to allow the caller to iterate over the internals of the implementing type.
It supports a simple iteration over a non-generic collection.
IEnumerator interface has methods to implement –
- MoveNext() and
- Reset()
The IEnumerator also provides a property Current which points to the current element that is touched during the iteration.
For example, if you have a variable of type List and you iterate over this collection variable as below –
IEnumerable<Book> items = new List<Book>();
foreach (var item in items)
{
// some code to access item of type Book
}
The above code snippet is equivalent to the below –
IEnumerable<Book> items = new List<Book>();
var itemsEnumerator = items.GetEnumerator();
Book item;
while (itemsEnumerator.MoveNext())
{
item = (Book)itemsEnumerator.Current;
// some code to access item of type Book
}
We don’t generally access or implement the IEnumerator interface. The IEnumerator is wrapped by the IEnumerable interface, which provides any implementing collection with a suitable Enumerator to loop over.
We can use a collection in a foreach loop only if it implements an IEnumerable (which supplies the required IEnumerator instance)
The IEnumerator provides methods using which we can loop through the elements in forward-only direction. The MoveNext() method moves only forward and not in reverse.
For cases where we may need to seek a previous element in the loop, or provide an alternative traversal strategy, we can consider implementing our own IEnumerator or using an Iterator design pattern.
What is an IEnumerable?
The IEnumerable is an interface that represents a collection of elements that can be iterated over. It doesn’t contain any other methods or properties, except a single method GetEnumerator – that returns an IEnumerator object.
In the above example, we are using an IEnumerable of type Book to store a collection of objects and are able to iterate over it using the GetEnumerator method of the IEnumerable.
With an IEnumerable, you can only work with an in-memory collection of objects.
Any collection in the System.Collection namespace internally implement IEnumerable and can be casted into an IEnumerable when required.
An IEnumerable is Covariant – meaning you can assign any implementation of an IEnumerable to a variable of type IEnumerable in generics. In the above example, we are assigning an object of type List<Book> to a variable of type IEnumerable<Book>, where the type List implements IEnumerable.
Hence IEnumerable is considered Covariant on the parameters. You can find more detail about Covariance, Invariance and Contravariance in my detailed article here.
IEnumerable types support lazy evaluation or deferred execution – a concept where data is not fetched and put in memory until it is required and the IEnumerable is called. This helps with better memory management and application performance. You will need to use a yield keyword to implement this – I have explained in detail about this here.
You can also extend the functionality over an IEnumerable such as filtering, sorting or projection by importing the LINQ library and using the appropriate extension method over the IEnumerable collection.
What is an IQueryable?
IQueryable is an interface in the System.Linq namespace.
It is a special collection using which you can query and store data from an external source such as a database. It provides an IQueryProvider, which is used to create LINQ providers used in data-centric operations such as LINQ-to-SQL etc.
IQueryable collections use a concept called deferred or remote query execution, where the query operations on a collection are not executed immediately. Instead, they are translated into a query language such as SQL and are executed on a remote data source like a database server.
We can use the extension method AsQueryable() to convert an ienumerable to iqueryable collection. This enables us to apply deferred execution over this collection.
var students = new List<Student>();
students.Add(new Student { });
students.Add(new Student { });
students.Add(new Student { });
students.Add(new Student { });
// filtered is of type IQueryable
var filtered = from r in students where r.Rank < 10 select r;
// evaluation takes place, result is printed
foreach (var student in filtered)
{
Console.WriteLine("Student: {student.Name}, Rank: {student.Rank}");
}
students.Add(new Student { });
students.Add(new Student { });
// evaluation takes place again, result is printed
foreach (var student in filtered)
{
Console.WriteLine("Student: {student.Name}, Rank: {student.Rank}");
}
On the other hand, you can use AsEnumerable() over an IQueryable collection to convert it into an IEnumerable collection.
Then a consolidated query that is the combination of all the conditions that have been applied over the IQueryable is generated and is executed to get a result on the external source.
AsEnumerable() or ToList() methods break deferred execution of an IQueryable. You can find more detailed information about this from my dedicated article on LINQ here.
Summary – IEnumerable vs IEnumerator vs IQueryable in C#
The following is a summary and comparison of the three interfaces.
IEnumerator | IEnumerable | IQueryable |
---|---|---|
System.Collections namespace | System.Collections namespace | System.Linq namespace |
provides the ability to iterate through the elements of a collection one by one | used to represent a collection of objects that can be enumerated or iterated over using a foreach loop | facilitates performing query and other filtering operations over a collection from a remote data source |
responsible for maintaining the state of the iteration, and provides methods to move next or track the current element | allows for forward-only iteration over a collection. does not support resetting the iteration or accessing elements by index directly. | provides the IQueryProvider, which is used to create LINQ providers used in data-centric operations such as LINQ-to-SQL etc |
doesn’t support any other operation other than the iteration | supports various operations over the collection via LINQ extension methods | supports various operations over the collection via LINQ extension methods |
suitable for working with in-memory collection operations | suitable for working with external data sources and databases | |
Supports lazy evaluation via yield keyword | Supports delayed remote querying by aggregating all operations and translating into single SQL query | |
Provides a collection with an implementation of IEnumerator | Implements IEnumerable to support looping | |
AsEnumerable converts an IQueryable to an IEnumerable | AsQueryable converts an IEnumerable to an IQueryable |
Conclusion
IEnumerator provides a way to loop over elements in a collection. IEnumerable is a collection that provides an Enumerator to loop over elements. IQueryable provides querying capabilities over a collection from various sources with efficient performance.
These are interdependent on one another and refer to each other internally as required.