Реализация методов интерфейса

Когда-то давно на собеседованиях по .NET часто задавали вопрос: "А может ли интерфейс содержать реализацию?"

С определенного времени этот вопрос утратил актуальность, поскольку начиная с С# 8, появилась возможность определять методы по умолчанию в интерфейсах.

Интересно, все ли современные интервьюеры знают об этом?! 😏

📎Давайте разберемся, что это за фича и как ей правильно пользоваться.

Раньше интерфейс был просто контрактом, который необходимо реализовать в классе-наследнике. И если вдруг в интерфейс добавляли новый метод, то наследникам необходимо было его сразу реализовать — иначе ошибки компиляции и боль.

А что теперь?

Интерфейс может не только требовать, но и предоставлять реализацию по умолчанию.

Пример:

public interface IPrinter { void Print(string text);

// Новый метод с реализацией по умолчанию! public void LogError(string errorMessage) { Console.WriteLine($"Ошибка: {errorMessage}"); // Кстати, можно вызывать другие методы интерфейса Print(errorMessage); } }

public class DocumentPrinter : IPrinter { // Класс обязан реализовать только основной метод public void Print(string text) { Console.WriteLine($"Документ на печать: {text}"); }

// Метод LogError не переопределяем, а используем тот что по умолчанию }

Использование:

IPrinter printer = new DocumentPrinter(); printer.Print("Очень важный документ"); // Работает как обычно printer.LogError("Заканчивается краска!"); //Работает! Вызовется метод по умолчанию.

// Выведется на экран: // Документ на печать: Очень важный документ // Ошибка: Заканчивается краска! // Документ на печать: Заканчивается краска!

Как применять на практике?

Пожалуй, самый основной сценарий использования — это расширение своих библиотек через интерфейсы, не ломая обратную совместимость.

Все, кто реализовывал ваш интерфейс, продолжат работать без изменений, получив возможность использовать реализацию метода по умолчанию, который поставляется с интерфейсом.

‼️ Важно:

  • Работает только через интерфейс Обратите внимание, в примере переменная printer имеет тип IPrinter. Если бы мы написали Printer printer = new Printer();, то метод был бы недоступен (если он не переопределен в классе).

  • Множественное наследование Класс может реализовывать несколько интерфейсов, и если в них есть методы с одинаковой сигнатурой, возникает конфликт. Компилятор заставит вас явно указать, какой метод использовать (с помощью явной реализации).

📌 Дефолтные методы в интерфейсах ломают старые представления о том, что интерфейс — это только контракт.

Используйте их для безболезненного расширения вашего кода!