JavaRush /Курсы /C# SELF /Интерфейсы в стандартной библиотеке .NET

Интерфейсы в стандартной библиотеке .NET

C# SELF
24 уровень , 3 лекция
Открыта

1. Классификация ключевых интерфейсов

Если вы думаете, что интерфейсы — это игрушка для архитекторов и "чистого кода", а в реальных делах без них можно обойтись, спешу вас удивить: любой серьезный проект на C# с головой уходит в интерфейсы. Почему? Потому что практически каждая часть .NET стандартной библиотеки устроена через интерфейсы! Без них вы не сможете ни работать с коллекциями, ни читать файлы, ни даже фильтровать коллекции с LINQ.

Интерфейсы — это фундамент полиморфизма и расширяемости в .NET, и именно они определяют то, как все "кубики" фреймворка складываются друг с другом.

В .NET стандартная библиотека буквально нашпигована интерфейсами. Чтобы не утонуть в этом море, предлагаю следующую классификацию (разумеется, она неполная — жизни не хватит объять все!):

Категория Интерфейсы Для чего нужны?
Коллекции
IEnumerable
,
IEnumerator
,
IList
,
ICollection
,
IDictionary
Перебор, изменение, доступ по индексу, работа с парами ключ-значение
Работа с ресурсами
IDisposable
Освобождение ресурсов (файлы, соединения, потоки)
Сравнение
IComparable
,
IComparer
,
IEquatable
Упорядочивание, сравнение, уникальность объектов
Сериализация
ISerializable
Превращение объектов в поток байтов (и обратно)
LINQ и запросы
IQueryable
,
IQueryProvider
Поддержка сложных запросов (например, к БД)
Асинхронность
IAsyncEnumerable
,
IAsyncDisposable
Асинхронный перебор и очистка ресурсов
События и уведомления
INotifyPropertyChanged
,
INotifyCollectionChanged
Реакция на изменения свойств/коллекций
Дата и время
IFormattable
Кастомное форматирование строк
Структуры данных
IStructuralComparable
,
IStructuralEquatable
Глубокое сравнение коллекций и кортежей
Потоки/ввод-вывод
IStream
,
IAsyncDisposable
,
IObserver
,
IObservable
Работа с потоками, push/pull-уведомления

Важно! Многие из вышеперечисленных интерфейсов имеют типы-параметры: List<string>. Обычно они используются в коллекциях и для интерфейсов коллекций Int[] → List<int>. Подробнее мы их разберем через пару уровней, когда будем знакомиться с коллекциями.

2. Интерфейсы коллекций

IEnumerable и IEnumerator — ваш пропуск в foreach

Практически каждая коллекция в .NET реализует интерфейс IEnumerable или даже его обобщенную версию IEnumerable<T>. Именно этот интерфейс позволяет вам использовать волшебный синтаксис foreach:

List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // работает, потому что List<int> реализует IEnumerable<int>
{
    Console.WriteLine(n);
}

Вот так этот интерфейс выглядит в минималистичной версии:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerator — это интерфейс самого "переборщика", которому доверено ходить по вашей коллекции:

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

В реальных задачах вы очень часто будете передавать параметры и возвращать значения типа IEnumerable<T>. Например, метод, возвращающий все четные числа из массива:

public IEnumerable<int> GetEvenNumbers(int[] array)
{
    foreach (var x in array)
        if (x % 2 == 0) yield return x;
}

Да, ключевое слово yield делает магию — реализует интерфейс за вас, но об этом подробнее позже.

ICollection и IList — коллекции с доступом по индексу и изменением

Если вам нужен не просто перебор, а возможность добавлять/удалять элементы или даже получать их по индексу, нужны более специализированные интерфейсы:

  • ICollection<T> — добавляет методы Add, Remove, а также свойство Count.
  • IList<T> — расширяет список возможных операций, включая доступ по индексу (this[int index]).
public void PrintFirstItem(IList<string> list)
{
    if (list.Count > 0)
        Console.WriteLine(list[0]);
}

List<T> реализует и IEnumerable<T>, и ICollection<T>, и IList<T>. Это очень удобно — можно работать с ним как с простым перечисляемым списком или использовать все богатство методов.

IDictionary<TKey, TValue> — пары "ключ-значение"

Если вы любите работать со словарями (а это случается очень часто!), используйте интерфейс IDictionary<TKey, TValue>. Он гарантирует, что вы можете по ключу получить значение и наоборот.

public void PrintAllPairs(IDictionary<string, int> ages)
{
    foreach (var pair in ages)
        Console.WriteLine($"{pair.Key}: {pair.Value}");
}

Здесь ages может быть чем угодно, хоть Dictionary<string, int>, хоть каким-нибудь SortedDictionary<string, int> — главное, что они реализуют этот интерфейс!

3. Интерфейс IDisposable: правильная работа с ресурсами

Весь ввод-вывод, работа с файлами, сетевыми соединениями, базами данных в .NET завязаны на интерфейс IDisposable. Этот интерфейс определяет жизненно важный контракт: если у объекта есть неуправляемые ресурсы, его нужно "очистить" после использования. Да, это тот парень, что сделал возможным синтаксис using:

using (StreamReader reader = new StreamReader("file.txt"))
{
    // Работаем с файлом, а после using файл гарантированно закрывается!
    string line = reader.ReadLine();
}

Как устроен сам интерфейс? Смешно просто:

public interface IDisposable
{
    void Dispose();
}

Но каждая "серьезная" библиотека реализует его! Подробнее о хороших практиках работы с этим интерфейсом читайте в официальной документации.

4. Интерфейсы для сравнения и уникальности

IComparable<T> и IComparer<T>

Если вы хотите, чтобы коллекция ваших объектов могла быть отсортирована, объекты должны уметь сравнивать себя между собой. Для этого и существует интерфейс IComparable<T>:

public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public int CompareTo(Student? other)
    {
        // Сортировка по убыванию баллов
        if (other == null) return 1;
        return other.Score.CompareTo(this.Score);
    }
}

// Теперь можно сортировать студентов:
var students = new List<Student> { ... };
students.Sort();

Подробнее в документации Microsoft.

IComparer<T> позволяет определить сравнение вне класса — например, вы хотите иногда сортировать студентов по имени, а иногда по баллам:

public class NameComparer : IComparer<Student>
{
    public int Compare(Student? x, Student? y)
    {
        return string.Compare(x?.Name, y?.Name);
    }
}

IEquatable<T> — сравнение на равенство

Хотите, чтобы ваш объект участвовал в HashSet<T> (т.е. был "уникален" для коллекций)? Реализуйте IEquatable<T>:

public class Person : IEquatable<Person>
{
    public string Name { get; set; }
    public bool Equals(Person? other)
    {
        if (other == null) return false;
        return this.Name == other.Name;
    }
}

5. Интерфейсы для событий и уведомлений

Вам когда-нибудь хотелось, чтобы ваша программа "узнавала", что у объекта поменялись свойства? Например, вы хотите чтобы интерфейс обновлялся сам, когда пользователь меняет фамилию? Именно для этого существует интерфейс INotifyPropertyChanged. Он очень популярен в приложениях с графическим интерфейсом (например, WPF или Xamarin).

Сигнатура интерфейса проста:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler? PropertyChanged;
}

Пример реализации:

public class User : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }
}

Теперь любые привязки пользовательского интерфейса к этому объекту будут автоматически обновляться при изменении Name. Если вдруг заинтересуетесь — читайте официальную документацию.

6. Другие полезные интерфейсы

Интерфейс IFormattable: гибкое форматирование

Когда вы хотите, чтобы ваш объект мог красиво и разнообразно выводиться в строку (например, с разным количеством знаков после запятой), реализуйте интерфейс IFormattable:

public class Temperature : IFormattable
{
    public double Celsius { get; }
    public Temperature(double celsius) => Celsius = celsius;

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        // Не будем усложнять, просто покажем 'C' или 'F' в зависимости от формата
        if (format == "F")
            return $"{Celsius * 9 / 5 + 32} F";
        return $"{Celsius} C";
    }
}

Теперь вы можете звать temp.ToString("F", null) или temp.ToString("C", null). Вот почему DateTime, double, decimal и другие встроенные типы поддерживают гибкое форматирование.

Интерфейсы для асинхронности

С появлением асинхронности в C# понадобились новые интерфейсы для работы в асинхронном мире. Например, если ваш объект асинхронно освобождает ресурсы — реализуйте IAsyncDisposable:

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}

И теперь вместо using вы пишете await using!

С асинхронными перечислениями (await foreach) работает интерфейс IAsyncEnumerable<T>, который позволяет перебирать элементы не блокируя основной поток. Его используют, например, при чтении больших файлов по частям или при работе с потоками данных из интернета. Подробнее в уровне 58 :P

Интерфейсы сериализации: ISerializable

Если ваша задача — превращать объекты в поток байтов для сохранения/передачи (например, по сети), .NET предлагает интерфейс ISerializable. Он используется не так часто сегодня, потому что появились более удобные механизмы (например, через атрибуты и готовые сериализаторы), но упоминание заслуживает. Вот пример сигнатуры:

public interface ISerializable
{
    void GetObjectData(SerializationInfo info, StreamingContext context);
}

7. Применение интерфейсов на практике: реальное приложение

Допустим, вы пилите консольное приложение для учёта книг в библиотеке (ну а что, кто-то же должен!). Хотите, чтобы можно было выводить списки книг, сортировать их, фильтровать, загружать и сохранять данные. Благодаря знанию интерфейсов .NET вы сможете:

  • Возвращать разные типы коллекций через IEnumerable<Book>, чтобы пользователю не пришлось задумываться, массив это или список.
  • Добавить сортировку по разным критериям: реализуете IComparable<Book> для сортировки по названию и отдельный IComparer<Book> по автору.
  • Обеспечить эффективное освобождение ресурсов при работе с файлами через IDisposable.
  • Организовать фильтрацию книг по жанру или автору с помощью LINQ, который работает со всем, что реализует IEnumerable<T>.
  • Масштабировать приложение — например, заменить файловое хранилище на подключение к базе данных, если ваши классы оперируют через интерфейсы (IBookStorage), а не через конкретные реализации.

8. Типичные ошибки и особенности

Одна из самых распространённых ошибок новичков — использовать конкретные реализации ("List") вместо интерфейсов ("IEnumerable", "IList") при объявлении переменных и аргументов методов. ПОМНИТЕ: если вы "программируете на интерфейсах", ваш код будет гибким и легко расширяемым.

Иногда нужно быть внимательным, выбирая, какой уровень интерфейса использовать — например, если вы пишете метод, которому нужна только возможность перебора, используйте IEnumerable<T>, а не IList<T>, чтобы не навязывать лишних ограничений.

Ещё одна тонкость: если у класса много интерфейсов, и между ними есть совпадающие методы или свойства, придется использовать явную реализацию интерфейса (explicit implementation), иначе компилятор вас не поймёт.

2
Задача
C# SELF, 24 уровень, 3 лекция
Недоступна
Проверка интерфейса IEnumerable
Проверка интерфейса IEnumerable
2
Задача
C# SELF, 24 уровень, 3 лекция
Недоступна
Сортировка с интерфейсом IComparable
Сортировка с интерфейсом IComparable
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 34
5 февраля 2026
Самая тупая лекция во всем курсе на данный момент. Поясняют использование интерфейсов через темы, которые будут рассмотрены ПОЗЖЕ. Что мы понять из нее должны? Ничего? Допустим я умею в Питон, и потому мне хотя бы понятно о чем идет речь. Как быть тем, для кого это первый курс по программированию? Поместите эту лекцию после всех используемых тем - и она обретет смысл!