Ответ
Эти интерфейсы образуют иерархию, где каждый последующий добавляет новую функциональность к предыдущему. Выбор интерфейса для возврата из метода или использования в API определяет контракт на возможности работы с коллекцией.
Иерархия: IEnumerable<T> ← ICollection<T> ← IList<T>
1. IEnumerable<T> — Перечисление (Enumeration)
- Минимальный контракт: «Я могу отдавать элементы по одному».
- Ключевой метод:
GetEnumerator(). - Что позволяет: Только последовательное чтение с помощью
foreachили LINQ. - Не позволяет: Проверять количество элементов, добавлять, удалять, обращаться по индексу.
- Идеален для возврата из методов, где нужно только прочитать данные, или для приема параметров, которые нужно только перебрать.
// Метод принимает минимальный интерфейс — только для чтения.
public void ProcessItems(IEnumerable<Product> products)
{
foreach (var product in products) // Единственная гарантированная операция.
{
Console.WriteLine(product.Name);
}
// products.Add(...) // НЕ КОМПИЛИРУЕТСЯ!
}
2. ICollection<T> — Коллекция (Collection)
- Расширяет
IEnumerable<T>. - Добавляет: Модификацию коллекции (добавление, удаление, очистка) и свойство
Count. - Что позволяет: Всё, что
IEnumerable<T>, плюсAdd,Remove,Clear,Contains,CopyToи получение точного количества элементов. - Не позволяет: Обращаться к элементам по индексу.
- Представлен такими типами, как
HashSet<T>,LinkedList<T>,Queue<T>,Stack<T>.
public void FillCollection(ICollection<string> bag)
{
bag.Add("item1"); // Можно модифицировать.
bag.Add("item2");
Console.WriteLine($"Added {bag.Count} items"); // Можно узнать количество.
// var x = bag[0]; // НЕ КОМПИЛИРУЕТСЯ! Нет индексатора.
}
3. IList<T> — Список (List)
- Расширяет
ICollection<T>. - Добавляет: Прямой доступ по индексу (чтение и запись) и методы для работы с индексами (
Insert,RemoveAt,IndexOf). - Что позволяет: Всё, что
ICollection<T>, плюсlist[0],Insert(0, item),RemoveAt(0). - Гарантирует порядок элементов.
- Основная реализация:
List<T>, массивы (T[]).
public void ReorderList(IList<int> numbers)
{
if (numbers.Count > 1)
{
// Полный доступ для чтения и записи по индексу.
var temp = numbers[0];
numbers[0] = numbers[^1]; // numbers[numbers.Count - 1]
numbers[^1] = temp;
}
}
Правило выбора:
- Возвращайте или принимайте
IEnumerable<T>, если нужен только перебор (максильная гибкость для вызывающего кода). - Используйте
ICollection<T>, если ваш метод должен добавлять/удалять элементы в переданную коллекцию, но ему не важен индекс. - Используйте
IList<T>, если критически важен прямой доступ или изменение по индексу.
Отдельно стоит IDictionary<TKey, TValue> — это параллельная ветвь для коллекций пар «ключ-значение» (Dictionary<TKey, TValue>, SortedDictionary<TKey, TValue>).