1. Введение
Итак, представьте себе: у вас есть коллекция… а вам нужно быстро получить третий, седьмой или, скажем, нулевой элемент. Или поменять их местами. В массиве это делается легко — по индексу (array[3]). А что насчёт коллекций? Ведь не все коллекции одинаково индексируемы!
Здесь на сцену выходит интерфейс IList<T> — универсальный контракт, который требует от коллекции поддерживать работу с элементами по индексу. Если коротко: у вас есть коллекция, которая реализует IList<T>, — значит, можно смело обращаться к её элементам по индексу (как в массиве) и изменять их на лету.
Аналогия:
Вспомните библиотечную карточку: у каждой книги есть свой порядковый номер на полке, и вы всегда можете подойти к "третей по счёту" книге и взять её. Вот так ведёт себя коллекция, поддерживающая IList<T>.
2. Общая структура и методы IList<T>
Интерфейс IList<T> — герой многих коллекций. Он расширяет ICollection<T> (а тот, в свою очередь, IEnumerable<T>, что позволяет проходить по коллекции с помощью циклов), и добавляет самое важное: работу с индексом.
Схема наследования интерфейсов:
IEnumerable<T>
▲
│
ICollection<T>
▲
│
IList<T>
Основные члены интерфейса IList<T>
| Член | Назначение |
|---|---|
|
Получить или установить элемент по индексу |
|
Найти индекс первого вхождения элемента |
|
Вставить элемент в заданную позицию |
|
Удалить элемент по индексу |
Все остальные члены, такие как Add, Remove, Clear, Contains, приходят из интерфейса ICollection<T>.
Ключевая фишка: индексатор
Главная "фишка" IList<T> — наличие индексатора. Это такой синтаксический сахар, благодаря которому можно писать:
var myList = new List<int> { 10, 20, 30 };
int secondValue = myList[1]; // Получаем 20
myList[2] = 42; // Меняем третий элемент
3. Какие коллекции реализуют IList<T>
В стандартной библиотеке .NET многие знакомые структуры поддерживают интерфейс IList<T>. Давайте посмотрим на самые популярные:
| Коллекция | Индексация | Описание |
|---|---|---|
|
Да | Динамический массив |
|
Да | Обычные массивы индексируемы |
|
Да | Используется для data binding |
|
Да | Список с уведомлениями |
|
Да | Базовый класс для коллекций |
Внимание:
LinkedList<T> и
HashSet<T>
НЕ реализуют
IList<T>, потому что у них нет быстрой индексации (да, у
LinkedList<T> нет
list[5]!).
4. Примеры использования IList<T>
Получение и установка по индексу
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
IList<string> fruits = new List<string> { "Яблоко", "Банан", "Груша" };
// Получение второго элемента
string fruit = fruits[1];
Console.WriteLine(fruit); // Банан
// Замена третьего элемента
fruits[2] = "Апельсин";
Console.WriteLine(fruits[2]); // Апельсин
}
}
Вставка и удаление по индексу
fruits.Insert(1, "Киви"); // Вставляем "Киви" на второе место
// Список теперь: "Яблоко", "Киви", "Банан", "Апельсин"
fruits.RemoveAt(0); // Удаляем первый элемент ("Яблоко")
// Список теперь: "Киви", "Банан", "Апельсин"
Поиск индекса элемента
int index = fruits.IndexOf("Апельсин"); // Вернёт индекс (2) или -1, если не найден
if (index != -1)
Console.WriteLine("Апельсин находится на позиции: " + index);
else
Console.WriteLine("Апельсин не найден");
5. Особенности реализации и типичные ошибки
Работая с IList<T>, легко попасть в пару "ловушек", особенно если забыть, что индексация начинается с нуля, а длина коллекции — это актуальное количество элементов.
Например, попытка обратиться к несуществующему элементу:
Console.WriteLine(fruits[100]); // IndexOutOfRangeException!
Индексы в C# — это как куры-несушки: начинают с нуля, а не с одного. Если в списке 4 элемента, последний доступный индекс — 3.
Также стоит помнить, что не все реализации IList<T> одинаково быстры. Например, у массива или у List<T> доступ по индексу мгновенный (O(1)), а вот если бы вы реализовали свою коллекцию на базе связного списка и зачем-то навесили на неё IList<T>, операция могла бы стать медленной. Хотя стандартная библиотека так не делает.
И ещё момент: если вы работаете с массивом как с IList<T>, то можете менять элементы, но не менять размер массива. Методы Add, Remove, Insert и т.п. для массива вызовут NotSupportedException.
int[] myArray = { 1, 2, 3 };
IList<int> listView = myArray; // Upcast
listView[0] = 42; // Работает!
listView.Add(99); // Бросит NotSupportedException
6. Практическое применение и зачем это нужно
В реальных проектах почти каждая вторая коллекция — это что-то, реализующее IList<T>, потому что удобно иметь возможность быстро обращаться к элементам по номеру, изменять их, вставлять и удалять по позиции. Например:
- Интерфейсные свойства во ViewModel WPF или WinForms, куда байндятся списки UI-элементов.
- Реализация алгоритмов сортировки, поиска, перестановки, где нужен доступ по индексу.
- Модули импорта/экспорта данных, которые работают с динамическим списком объектов.
На собеседовании вопрос про отличия IEnumerable<T>, ICollection<T> и IList<T> — классика. Зная, за что отвечает каждый уровень, вы можете уверенно объяснить интервьюеру, почему HashSet<T> не реализует IList<T> (потому что уникальность важнее порядка и индексов!).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ