Реализация методов интерфейса
Когда-то давно на собеседованиях по .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();, то метод был бы недоступен (если он не переопределен в классе).
-
Множественное наследование Класс может реализовывать несколько интерфейсов, и если в них есть методы с одинаковой сигнатурой, возникает конфликт. Компилятор заставит вас явно указать, какой метод использовать (с помощью явной реализации).
📌 Дефолтные методы в интерфейсах ломают старые представления о том, что интерфейс — это только контракт.
Используйте их для безболезненного расширения вашего кода!