Підрозділ 4.10
Наслідування узагальнених типів
Показує різні варіанти наслідування узагальнених класів і перенесення обмежень у похідні типи.
4.10. Наслідування узагальнених типів
Узагальнені класи можуть успадковуватися один від одного — так само як і звичайні. При цьому є чотири принципово різних варіанти того, як похідний клас взаємодіє з параметром типу базового. Вибір між ними визначається тим, наскільки гнучким або конкретним має бути похідний клас.
Як базовий клас для всіх прикладів використаємо MedicalRecord<T>:
abstract class MedicalRecord<T>
{
public T Id { get; }
public string Description { get; set; }
public MedicalRecord(T id, string description)
{
Id = id;
Description = description;
}
public override string ToString() => $"[{Id}] {Description}";
}
Варіант 1: передача параметра далі — Child : Base
Похідний клас залишається узагальненим і передає свій параметр T у базовий клас. Це найбільш гнучкий варіант — конкретний тип визначається лише при створенні екземпляра:
FlexibleRecord<int> і FlexibleRecord<string> — це два різних класи, кожен зі своїм типом Id. Похідний клас просто «пробрасує» параметр у базовий.
Варіант 2: фіксація типу — Child : Base
Похідний клас є звичайним (неузагальненим) і фіксує конкретний тип для базового. Це доречно, коли для конкретної предметної ситуації тип ідентифікатора відомий заздалегідь:
InpatientRecord — звичайний клас без власних параметрів типу. Він завжди має Id типу string, і його можна зберігати у змінній MedicalRecord<string>.
Варіант 3: власний параметр при фіксованому базовому — Child : Base
Похідний клас є узагальненим з власним параметром T, але у базового клас тип зафіксований. Це дозволяє додавати нові generic-поля незалежно від базового:
TaggedRecord<T> успадковує MedicalRecord<int> — тому Id завжди int. Але сам клас залишається параметричним за T, що використовується для поля Tag.
Варіант 4: розширення базового параметра — Child : Base
Похідний клас передає базовому параметр T і одночасно додає власний новий параметр. Це найбільш потужний варіант для побудови складних ієрархій:
AnnotatedRecord<T, TNote> повністю гнучкий: T визначає тип Id (як у базовому), а TNote — тип додаткової анотації. Обидва параметри незалежні.
Успадкування обмежень
Якщо базовий клас встановлює обмеження на параметр типу, похідний клас зобов'язаний підтримати або посилити це обмеження для того самого параметра:
Правило просте: якщо базовий клас має where T : class, то і похідний повинен вказати where T : class або конкретніше обмеження (наприклад, where T : Patient). Це гарантує, що компілятор зможе перевіряти обмеження на всіх рівнях ієрархії.
Підсумок: коли що використовувати
| Варіант | Синтаксис | Коли застосовувати |
|---|---|---|
| Передача T | Child<T> : Base<T> |
Похідний залишається гнучким |
| Фіксація типу | Child : Base<string> |
Тип відомий — клас стає конкретним |
| Свій T, фіксований базовий | Child<T> : Base<int> |
Додаємо гнучкість у нових полях |
| Розширення параметрів | Child<T, K> : Base<T> |
Максимальна гнучкість |