IEnumerable<T> / IList<T>
| Since: | C# 1.0(2002) |
|---|
The base interfaces for abstracting collections: IEnumerable<T> and IList<T>. Using them as parameter or return types in methods enables flexible, decoupled design.
Syntax
IEnumerable<T> variable = new List<T>(); // IList<T>: an interface that supports index access, addition, and removal. IList<T> variable = new List<T>(); // IReadOnlyList<T>: a read-only list interface. IReadOnlyList<T> variable = new List<T>();
Interface List
| Interface | Description |
|---|---|
| IEnumerable<T> | The most basic interface, supporting only iteration with foreach. LINQ extension methods are available. |
| ICollection<T> | Provides element count (Count), along with add, remove, and contains operations. |
| IList<T> | Extends ICollection with index-based access ([i]), insertion, and removal. |
| IReadOnlyList<T> | A read-only list. Supports Count and index access, but does not allow modifications. |
| IReadOnlyCollection<T> | A read-only collection that only supports Count and foreach. |
Sample Code
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
// Using IEnumerable<T> as the parameter type lets you accept a List, array, HashSet, or any other collection.
void Print(IEnumerable<string> items)
{
foreach (string item in items) {
Console.Write(item + " ");
}
Console.WriteLine();
}
List<string> list = new List<string> { "C#", "Java", "Python" };
string[] array = { "Go", "Rust", "Swift" };
HashSet<string> set = new HashSet<string> { "Ruby", "PHP" };
Print(list); // C# Java Python
Print(array); // Go Rust Swift
Print(set); // Ruby PHP
// With IList<T>, you can also use index access.
IList<int> numbers = new List<int> { 10, 20, 30 };
Console.WriteLine(numbers[1]); // 20
numbers.Add(40);
Console.WriteLine(numbers.Count); // 4
// Use IReadOnlyList<T> to prevent external modifications.
IReadOnlyList<string> readOnly = new List<string> { "A", "B", "C" };
Console.WriteLine(readOnly[0]); // A
Console.WriteLine(readOnly.Count); // 3
// readOnly.Add("D"); // Compile error.
// LINQ works with IEnumerable<T>.
IEnumerable<int> sequence = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(sequence.Where(x => x % 2 == 0).Sum()); // 6
This produces the following output:
dotnet run C# Java Python Go Rust Swift Ruby PHP 6
IList<T> and IReadOnlyList<T>
IList<T> supports index access, addition, and removal. IReadOnlyList<T> is read-only, making it useful when you want to prevent external modifications.
IListSample.cs
using System;
using System.Collections.Generic;
IList<int> numbers = new List<int> { 10, 20, 30 };
Console.WriteLine(numbers[1]); // 20
numbers.Add(40);
Console.WriteLine(numbers.Count); // 4
IReadOnlyList<string> readOnly = new List<string> { "A", "B", "C" };
Console.WriteLine(readOnly[0]); // A
Console.WriteLine(readOnly.Count); // 3
// readOnly.Add("D"); // Compile error
This produces the following output:
dotnet run 20 4 A 3
Common Mistakes
Common Mistake: Exposing the Concrete List<T> Directly
Exposing a class's internal data as List<T> allows external code to add or remove elements freely. Use IReadOnlyList<T> or AsReadOnly() to make it read-only.
using System;
using System.Collections.Generic;
class Team
{
// NG: exposing List directly allows external modification
public List<string> Members = new List<string> { "Ikari Shinji", "Ayanami Rei" };
}
Team team = new Team();
team.Members.Add("Soryu Asuka"); // can be modified from outside
Console.WriteLine(team.Members.Count); // 3
The corrected version looks like this:
using System;
using System.Collections.Generic;
class Team
{
private List<string> _members = new List<string> { "Ikari Shinji", "Ayanami Rei" };
// OK: expose as IReadOnlyList to prevent external modification
public IReadOnlyList<string> Members => _members;
public void AddMember(string name) => _members.Add(name);
}
Team team = new Team();
// team.Members.Add("Soryu Asuka"); // Compile error (read-only)
team.AddMember("Soryu Asuka"); // add via method instead
Console.WriteLine(team.Members.Count); // 3
This produces the following output:
dotnet run 3
Notes
As a best practice, choose the least restrictive interface that still provides the functionality you need for a method parameter. Use IEnumerable<T> when you only need to iterate, ICollection<T> when you also need the element count, and IList<T> when you need index-based access.
When exposing internal class data to the outside, IReadOnlyList<T> is the right choice. Exposing the concrete List<T> directly allows external code to modify your data.
For collections without duplicates, see HashSet<T>. For Queue and Stack, see Queue<T> / Stack<T>.
If you find any errors or copyright issues, please contact us.