Comparison between List, IList, and IEnumerable
Categories:
List, IList, and IEnumerable: Understanding C# Collection Interfaces
Explore the fundamental differences and use cases for IEnumerable, IList, and List in C#. Learn when to choose each for optimal performance and flexibility in your .NET applications.
In C#, working with collections of data is a common task. The .NET framework provides several interfaces and classes to manage these collections, with IEnumerable<T>
, IList<T>
, and List<T>
being among the most frequently encountered. While they all deal with sequences of objects, they offer different levels of functionality, performance characteristics, and flexibility. Understanding their distinctions is crucial for writing efficient, maintainable, and robust C# code.
IEnumerable: The Foundation of Iteration
IEnumerable<T>
is the most basic interface for collections in C#. It defines a single method, GetEnumerator()
, which returns an enumerator that can iterate over the collection. This makes IEnumerable<T>
ideal for scenarios where you only need to read data sequentially, without modifying the collection or accessing elements by index.
public static void ProcessData(IEnumerable<string> data)
{
foreach (var item in data)
{
Console.WriteLine(item);
}
// data.Add("New Item"); // Compile-time error: IEnumerable does not support Add
}
public static void Main()
{
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
ProcessData(names); // Works because List<T> implements IEnumerable<T>
}
Using IEnumerable
IEnumerable<T>
if you only need to iterate over the items. This promotes loose coupling and allows your method to work with any collection type that supports enumeration, including custom iterators and LINQ query results.IList: Indexed Access and Modification
IList<T>
extends IEnumerable<T>
and adds capabilities for indexed access, adding, removing, and updating elements. It provides methods like Add()
, Remove()
, Insert()
, and a property for indexed access (this[int index]
). This interface is suitable when you need to manipulate the collection's contents or access elements directly by their position.
public static void ManipulateList(IList<string> list)
{
list.Add("David");
Console.WriteLine($"First element: {list[0]}");
list[1] = "Bobby";
list.RemoveAt(2);
foreach (var item in list)
{
Console.WriteLine(item);
}
}
public static void Main()
{
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
ManipulateList(names); // Works because List<T> implements IList<T>
}
Demonstrating IList
IList<T>
allows modification, be aware that modifying a collection while iterating over it (e.g., using a foreach
loop) can lead to InvalidOperationException
if the underlying collection doesn't support concurrent modification. Always consider using a for
loop or creating a copy if modifications are necessary during iteration.List: The Concrete Implementation
List<T>
is a concrete class that implements both IEnumerable<T>
and IList<T>
(among others like ICollection<T>
). It's a dynamic array that can grow or shrink as needed, providing efficient indexed access and modification operations. List<T>
is often the default choice when you need a general-purpose, mutable collection in memory.
public static void UseListDirectly()
{
List<int> numbers = new List<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Insert(0, 5);
Console.WriteLine($"Count: {numbers.Count}");
Console.WriteLine($"Element at index 1: {numbers[1]}");
foreach (var num in numbers)
{
Console.WriteLine(num);
}
}
public static void Main()
{
UseListDirectly();
}
Basic usage of the List
classDiagram IEnumerable<T> <|-- ICollection<T> ICollection<T> <|-- IList<T> IList<T> <|-- List<T> class IEnumerable~T~{ +IEnumerator<T> GetEnumerator() } class ICollection~T~{ +int Count +bool IsReadOnly +void Add(T item) +void Clear() +bool Contains(T item) +void CopyTo(T[] array, int arrayIndex) +bool Remove(T item) } class IList~T~{ +T this[int index] +int IndexOf(T item) +void Insert(int index, T item) +void RemoveAt(int index) } class List~T~{ +List() +void Add(T item) +T this[int index] +void Sort() +void Reverse() +int BinarySearch(T item) // ... many other methods }
Inheritance hierarchy and key members of IEnumerable, IList, and List
The diagram above illustrates the inheritance relationship. List<T>
implements IList<T>
, which in turn implements ICollection<T>
, and ICollection<T>
implements IEnumerable<T>
. This means a List<T>
object can be treated as an IList<T>
, ICollection<T>
, or IEnumerable<T>
, depending on the required functionality.