Підрозділ 7.4
Успадкування інтерфейсів
Пояснює успадкування інтерфейсів, приховування методів через new і правила доступності похідних інтерфейсів.
7.4. Успадкування інтерфейсів
Інтерфейси не лише описують контракти — вони можуть розширювати один одного. Механізм успадкування інтерфейсів (interface inheritance) дозволяє будувати ієрархії контрактів: від загального до конкретного. Клас, який реалізує похідний інтерфейс, зобов'язаний виконати контракти всіх інтерфейсів у ланцюжку.
Це дає змогу проектувати системи поступово: спочатку визначати базові здібності об'єкта, потім нашаровувати на них більш спеціалізовані вимоги — не змінюючи вже написаний код.
Базовий синтаксис
Синтаксис успадкування інтерфейсів ідентичний синтаксису успадкування класів — через двокрапку:
interface IExaminable
{
void Examine(); // провести огляд пацієнта
}
interface IDiagnosable : IExaminable
{
void Diagnose(); // поставити діагноз
}Тут IDiagnosable розширює IExaminable. Це означає, що IDiagnosable включає в себе всі члени IExaminable плюс власні.
Будь-який клас чи структура, що реалізує IDiagnosable, зобов'язана реалізувати обидва методи — і Examine(), і Diagnose():
Зверніть увагу: змінна типу IExaminable дає доступ лише до Examine(), хоч і містить той самий об'єкт. Тип змінної визначає, який «зріз» контракту видно у конкретному місці коду.
Ланцюжок успадкування
Інтерфейси можуть утворювати ланцюжки довільної довжини. Кожен новий інтерфейс у ланцюжку додає нові вимоги до контракту:

Клас Doctor, що реалізує ITreatable, зобов'язаний надати реалізацію всіх трьох методів — Examine(), Diagnose() і Treat(). Компілятор перевіряє це під час збірки: якщо хоча б один метод не реалізовано — отримаємо помилку компіляції.
Множинне успадкування інтерфейсів
На відміну від класів, інтерфейс може успадковувати відразу кілька батьківських інтерфейсів. Це одна з ключових відмінностей інтерфейсів від класів у C# — множинне успадкування реалізації заборонене для класів, але множинне успадкування контракту — цілком допустиме для інтерфейсів.
Синтаксис: після двокрапки перераховуємо всі базові інтерфейси через кому:
interface IClinicAppointment : ISchedulable, IPayable
{
// ...
}
Клас Appointment, що реалізує IClinicAppointment, зобов'язаний виконати контракти обох батьківських інтерфейсів — ISchedulable і IPayable — а також власні члени IClinicAppointment:
Ключове слово new — приховування членів базового інтерфейсу
Коли базовий інтерфейс має метод із реалізацією за замовчуванням (default interface implementation), похідний інтерфейс може її перевизначити за допомогою ключового слова new. Це приховування (hiding), а не перевизначення у звичному розумінні.
Поведінка залежить від типу змінної, а не від фактичного об'єкта. Якщо змінна оголошена як IDiagnosable — буде викликана його версія Examine(). Якщо IExaminable — оригінальна. Це принципово відрізняється від поліморфізму через virtual/override, де завжди викликається версія фактичного об'єкта.
Клас може повністю взяти контроль і реалізувати Examine() самостійно — тоді обидва типи повертатимуть ту саму реалізацію:
Модифікатори sealed та abstract для інтерфейсів
Інтерфейси мають власні правила щодо модифікаторів доступу та структури:
sealed — заборонений для інтерфейсів. Модифікатор sealed у класах забороняє успадкування. Для інтерфейсів такого механізму не існує — будь-який інтерфейс можна успадкувати. Це відповідає природі інтерфейсу як відкритого контракту.
abstract — надлишковий для інтерфейсів. Інтерфейс вже за визначенням є абстрактним: він описує контракт без (обов'язкової) реалізації. Тому ключове слово abstract на рівні інтерфейсу не має сенсу і не допускається компілятором.
Правила рівня доступу
При успадкуванні інтерфейсів діє таке саме правило, що й при успадкуванні класів: похідний інтерфейс не може бути менш обмеженим, ніж базовий.
Припустимо, що базовий інтерфейс є public — тоді похідний може бути або public, або internal:
public interface IExaminable
{
void Examine();
}
// Коректно: internal є більш суворим, ніж public
internal interface IDiagnosable : IExaminable
{
void Diagnose();
}Але не навпаки. Якщо базовий є internal, то похідний не може бути public — це порушує принцип інкапсуляції типів:
internal interface IExaminable
{
void Examine();
}
// ПОМИЛКА: IRunAction може бути лише internal,
// бо базовий IExaminable є internal
public interface IDiagnosable : IExaminable
{
void Diagnose();
}Компілятор поверне помилку: неможливо оголосити public тип, що успадковує internal тип, оскільки internal тип недоступний за межами збірки.
Підсумок
Успадкування інтерфейсів — потужний інструмент для побудови гнучких та розширюваних систем. Основні правила:
- Інтерфейс успадковує члени всіх своїх базових інтерфейсів.
- Клас, що реалізує похідний інтерфейс, зобов'язаний реалізувати весь ланцюжок.
- Інтерфейс може успадковувати кілька базових інтерфейсів одночасно.
sealedіabstractна рівні інтерфейсу не допускаються.newу похідному інтерфейсі приховує (а не перевизначає) метод базового.- Доступ похідного інтерфейсу не може бути менш суворим, ніж базового.