Understanding IEnumerable, ICollection, IList and IQueryable interfaces in C#
If you have ever thought about the differences among IEnumerable, ICollection, IList, and IQueryable; why these types are used as return types for methods, when declaring properties, and variables, then you are in luck.
Understanding the specific characteristics of these interfaces and scenarios where they are applicable is key, as this can improve your application and enhance your code. Now how do you decide on which one to use and when?
Let us examine each interface.
IEnumerable - System.Collections
IEnumerable
interface.
namespace System.Collections
{
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
}
IEnumerable is the base interface for all non-generic collections that can be enumerated. It contains a single method, GetEnumerator
, which returns an IEnumerator
. IEnumerator provides the ability to iterate through the collection by exposing Current
property and MoveNext
and Reset
methods.
In other words, it is a container that holds a list of items and provides the functionality to iterate through these items using foreach
statement.
IEnumerable
is the most basic interface for non-generic collections.
Its generic equivalent is IEnumerable<T>
. A base interface for collections in the System.Collections.Generic
namespace such as List<T>
, Dictionary<TKey,TValue>
, and Stack<T>
. IEnumerable<T>
comes with a lot more extension methods for filtering and performing other operations on a collection. It is important to note that elements in IEnumerable<T>
are read-only. You cannot modify items in a collection of type IEnumerable<T>
.
To remain compatible with methods that iterate non-generic collections,
IEnumerable<T>
implementsIEnumerable
. This allows a generic collection to be passed to a method that expects an IEnumerable object.
If you need a simple (read-only) collection, IEnumerable
or IEnumerable<T>
should suffice.
When querying data and returning an
IEnumerable
orIEnumerable<T>
, the execution will be LINQ-to-object. This means that all objects matching the original query will be loaded into memory from database. If filtering is applied to the original query, it will be executed after the objects have been loaded into memory.
In the example below, Take(2)
extension method is applied only after the results have been loaded to memory. Notice the SQL statement generated.
// using IEnumerable
IEnumerable<Book> query = _db.Book.Where(a => a.Title.StartsWith("T"));
query = query.Take(2);
// SQL statement generated
/**
SELECT [b].[Id], [b].[Author], [b].[ISBN], [b].[Title]
FROM [Book] AS [b]
WHERE [b].[Title] LIKE N'T%'
*/
In the above example, the SQL statement executed at database layer returns all records that match the WHERE
criteria, even though we are looking for first two records. Take(2)
filter is applied only after the results have been loaded into the application's memory.
When should you use IEnumerable
?
- You want to iterate over the elements in a collection -
IEnumerable
- You need read-only access to the collection -
IEnumerable<T>
- When retrieving all records - both.
- When records are already loaded into memory - both.
As a base interface for collections, IEnumerable
can work with nearly all collection types. When used as return type, the caller of your methods can convert the result to any collection type that implements IEnumerable
.
ICollection - System.Collections
namespace System.Collections
{
public interface ICollection : IEnumerable
{
int Count { get; }
bool IsSynchronized { get; }
object SyncRoot { get; }
void CopyTo(Array array, int index);
}
}
The ICollection
interface is the base interface for classes in the System.Collections
namespace and extends the IEnumerable
interface. It provides some properties like Count
which returns the number of elements contained in the collection. Its generic type is the ICollection<T>
interface.
ICollection of T - System.Collections.Generic
namespace System.Collections.Generic
{
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
int Count { get; }
bool IsReadOnly { get; }
void Add(T item);
void Clear();
bool Contains(T item);
void CopyTo(T[] array, int arrayIndex);
bool Remove(T item);
}
}
Unlike non-generic ICollection
interface, generic ICollection<T>
interface is widely used because of its extra functionalities to add, remove and search items in a collection. It inherits from collections of types IEnumerable<T>
and IEnumerable
, and can take advantage of extension methods in IEnumerable<T>
and GetEnumerator()
method of IEnumerable
.
When is this collection type useful?
By looking at the interface definition above. It defines a set of methods and properties for ICollection<T>
to help you figure out if your requirements can be met by this collection type.
Use ICollection<T>
when:
- You need the count of the collection.
- You plan to edit the collection.
- You plan to search the collection.
- You intend to perform filtering operations using extension methods such as
Where
provided inIEnumerable<T>
interface.
IList - System.Collections
namespace System.Collections
{
[DefaultMember("Item")]
public interface IList : ICollection, IEnumerable
{
object? this[int index] { get; set; }
bool IsFixedSize { get; }
bool IsReadOnly { get; }
int Add(object? value);
void Clear();
bool Contains(object? value);
int IndexOf(object? value);
void Insert(int index, object? value);
void Remove(object? value);
void RemoveAt(int index);
}
}
IList is a descendant of the ICollection
interface and it is the base interface for all non-generic lists. Its implementation falls into three categories:
- Read-only: Cannot be modified.
- Fixed-size: Does not allow the addition or removal of elements, but it allows the modification of existing elements.
- Variable-size: Allows the addition, removal, and modification of elements.
In addition to properties and methods provided in ICollection
, the IList
interface comes with extra functionalities such as Insert
, and RemoveAt
methods. Its generic version is the IList<T>
in System.Collections.Generic
.
If your requirements exceed functionalities provided in ICollection<T>
interface, then you should consider IList
or IList<T>
interface.
Use IList
or IList<T>
if:
- You want to modify the collection.
- You care about the positioning or ordering of elements in the collection.
IQueryable - System.Linq
namespace System.Linq
{
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
}
IQueryable
interface is intended for implementation by query providers that also implement IQueryable<T>
interface. It inherits the IEnumerable interface so that if it represents a query, the results of that query can be enumerated.
When using LINQ, IQueryable
converts LINQ expression to SQL statement which is executed on database layer.
IQueryable
can improve query performance in some cases. For example, let's say we want to return all Books in database where Title
starts with "T". Let's assume we already know that there are 20,000 Books and 500 book titles starts with a "T".
// using IEnumerable
IEnumerable<Book> query = _db.Book.Where(a => a.Title.StartsWith("T"));
query = query.Take(2); // 500 records returned, 2 records filtered
// SQL statement generated
/**
SELECT [b].[Id], [b].[Author], [b].[ISBN], [b].[Title]
FROM [Book] AS [b]
WHERE [b].[Title] LIKE N'T%'
*/
// Using IQueryable
IQueryable<Book> query = _db.Book.Where(a => a.Title.StartsWith("T"));
query = query.Take(2); // 2 records returned, 2 records filtered
// SQL statement generated
/**
SELECT TOP(@__p_0) [b].[Id], [b].[Author], [b].[ISBN], [b].[Title]
FROM [Book] AS [b]
WHERE [b].[Title] LIKE N'T%'
*/
Both queries look the same but from performance point of view, IQueryable<T>
returns records filtered at database layer. This improves both performance of the query and memory used. 2 records are filtered and returned instead of 500.
Use IQueryable
when:
- Writing LINQ to SQL or LINQ to Entities
- When filtering in (1.) is intended.
Conclusion
There are many more generic and non-generic collection interfaces in C#. In my opinion, IEnumerable, ICollection, IList and IQueryable are among the most widely used collection interfaces. I hope I was successful at expanding your understanding of these interfaces. For further reading please visit MSDN Documentation
Let me know what you think in the comment section.
Comments