Підрозділ 4.Q
Питання для самоконтролю
Питання для самоконтролю — Розділ 4. Об'єктно орієнтоване програмування 4.1. Успадкування 1. Що таке відношення is a ? Чим воно відрізняється від відношення has a ? Наведіть приклад неправильного застосування у
Питання для самоконтролю — Розділ 4. Об'єктно-орієнтоване програмування
4.1. Успадкування
Що таке відношення is-a? Чим воно відрізняється від відношення has-a? Наведіть приклад неправильного застосування успадкування там, де слід використовувати композицію.
Поясніть синтаксис
: base(...)у конструкторі похідного класу. Що буде, якщо базовий клас має лише параметризований конструктор, а похідний не викличеbase(...)?Що виведе цей код і в якому порядку?
class A { public A(string s) { Console.WriteLine($"A({s})"); } } class B : A { public B() : base("від B") { Console.WriteLine("B()"); } } class C : B { public C() { Console.WriteLine("C()"); } } new C();Чим відрізняються модифікатори
protectedтаprivateз точки зору ієрархії успадкування? Коли доцільно використовуватиprotected?Що таке
sealedклас? Чому у стандартній бібліотеці .NET класStringєsealed? Наведіть приклад ситуації, деsealedархітектурно виправданий.Чи передаються конструктори при успадкуванні? Що станеться, якщо базовий клас не має конструктора без параметрів і похідний клас не визначає власний конструктор?
Спроєктуйте невелику ієрархію для транспортних засобів: базовий клас
Vehicleз властивостямиMakeіSpeed, та два похідних —Car(зNumberOfDoors) іTruck(зLoadCapacity). Всі класи мають конструктори, що використовуютьbase(...).Від якого класу неявно успадковують усі класи в C#? Які методи це надає кожному об'єкту?
4.2. Перетворення типів
Поясніть різницю між upcasting і downcasting. Яке з них безпечне і виконується неявно, а яке несе ризик і вимагає явного синтаксису?
Що виведе цей код?
Patient patient = new Patient("Іван", 45, "Грип"); Person person = patient; Console.WriteLine(person.GetType().Name);Поясніть: чи є
personіpatientрізними об'єктами у пам'яті?Яку помилку спричинить наступний код і коли саме — під час компіляції чи виконання?
Person person = new Person("Сергій", 52); Patient patient = (Patient)person;У чому перевага оператора
asперед явним приведенням(Patient)person? Яке значення повертаєasу разі невдачі?Що робить вираз
person is Patient patient(C# 7+)? Чим він зручніший за комбінацію окремої перевірки типу і явного приведення?У масиві
Person[]зберігаються об'єкти типівPatient,DoctorіPerson. Напишіть цикл зswitch(pattern matching), що обробляє кожен тип окремо. Чому порядок гілокswitchважливий?Порівняйте
GetType() == typeof(Patient)таobj is Patient. В якому випадку кожен підхід повернеtrue, а в якомуfalse?Вас попросили реалізувати метод
PrintDetails(Person p), що виводить специфічну для кожного типу інформацію —DiagnosisдляPatient,SpecializationдляDoctor. Реалізуйте його двома способами: черезis-перевірку та черезswitchз pattern matching. Порівняйте читабельність.
4.3. Віртуальні методи та властивості
Що означає ключове слово
virtualу оголошенні методу? Що буде, якщо похідний клас не перевизначає віртуальний метод?Що таке поліморфізм у контексті віртуальних методів? Поясніть, чому наступний цикл виводить різний текст для кожного елемента масиву:
Person[] staff = { new Person("A",30), new Patient("Б",45,"Грип"), new Doctor("В",38,"Кардіо") }; foreach (Person p in staff) p.Print();Що таке пізнє зв'язування (late binding / dynamic dispatch)? Коли саме — під час компіляції чи виконання — вирішується, яка реалізація методу буде викликана?
Що виведе цей код?
class Person { public virtual void Print() => Console.WriteLine("Person"); } class Patient : Person { public override void Print() => Console.WriteLine("Patient"); } class Inpatient : Patient { public override void Print() { base.Print(); Console.WriteLine("Inpatient"); } } Person p = new Inpatient(); p.Print();Як можна перевизначити властивість у похідному класі? Чи зобов'язані збігатися accessor'и (
get/set) у базовому та похідному класі?Що означає
sealed override? Яку проблему він вирішує і коли виправданий?Спроєктуйте клас
Shapeз віртуальним методомArea(), що повертаєdouble. Реалізуйте похідні класиCircle(з радіусом) іRectangle(з шириною і висотою). Напишіть метод, що приймаєShape[]і виводить площу кожної фігури.Порівняйте два підходи до зміни поведінки в похідному класі: виклик
base.Print()перед власним кодом (розширення) та повна заміна реалізації без викликуbase. Коли доцільний кожен підхід?
4.4. Приховування методів та властивостей
Що таке приховування методу в C#? Яке ключове слово використовується і що станеться, якщо його не вказати?
Що виведе наступний код?
class A { public void Print() => Console.WriteLine("A"); } class B : A { public new void Print() => Console.WriteLine("B"); } A obj = new B(); obj.Print();Поясніть результат.
Коли використання
new(приховування) є єдиним або найкращим варіантом замістьoverride(перевизначення)?Чим синтаксично відрізняється приховування властивості від перевизначення? Як у прихованій властивості звернутися до оригінальної базової реалізації?
Чи можна приховати
constабоstatic readonlyполе у похідному класі? Який результат дасть наступний код?class Base { public const string Label = "Base"; } class Child : Base { public new const string Label = "Child"; } Console.WriteLine(Base.Label); Console.WriteLine(Child.Label);Поясніть ситуацію: є бібліотека з класом
Logger, у якого методWrite(string msg)не єvirtual. Ви хочете додати до нього форматування і створилиMyLogger : Logger. Як реалізувати власну версіюWrite? Які обмеження вашого рішення?Знайдіть потенційну проблему в наступному коді:
class Animal { public void Speak() => Console.WriteLine("..."); } class Dog : Animal { public new void Speak() => Console.WriteLine("Гав!"); } Animal[] animals = { new Dog(), new Dog() }; foreach (Animal a in animals) a.Speak();Що виведе цей код і чому це може бути несподіваним?
4.5. Відмінність перевизначення та приховування методів
Сформулюйте у двох реченнях ключову відмінність між
overrideіnewз точки зору зв'язування (binding). Що вирішує виклик у кожному випадку?Що таке таблиця віртуальних методів (VMT)? Як вона формується для класу і як використовується при виклику методу?
Заповніть таблицю: що виведуть такі виклики?
// override варіант: class A { public virtual void F() => Console.WriteLine("A"); } class B : A { public override void F() => Console.WriteLine("B"); } A x = new B(); x.F(); // ? B y = new B(); y.F(); // ? // new варіант: class C { public void F() => Console.WriteLine("C"); } class D : C { public new void F() => Console.WriteLine("D"); } C p = new D(); p.F(); // ? D q = new D(); q.F(); // ?Чому виклик віртуального методу дещо повільніший, ніж невіртуального? За яких умов ця різниця суттєва?
Якщо потрібно, щоб колекція
List<Person>при переборі викликала правильну реалізаціюPrint()для кожного об'єкту, яку техніку слід використовувати —overrideчиnew? Обґрунтуйте.Розробник написав такий код і дивується, чому
Print()уDoctorне викликається:class Person { public void Print() => Console.WriteLine("Person"); } class Doctor : Person { public new void Print() => Console.WriteLine("Doctor"); } Person[] team = { new Doctor("Коваль",38,"Кардіо") }; foreach (Person p in team) p.Print();Поясніть, у чому помилка і як її виправити.
Порівняйте у вигляді таблиці
overrideтаnewза критеріями: зв'язування, що визначає виклик (тип змінної / тип об'єкта), чи потрібенvirtualу базовому, VMT-поведінка.
4.6. Абстрактні класи та члени класів
Чому не можна створити екземпляр абстрактного класу? Яку роль він відіграє в ієрархії?
Порівняйте
abstractметод іvirtualметод за наявністю реалізації у базовому класі та зобов'язаннями похідних класів.Що виведе цей код або яку помилку він спричинить?
abstract class Shape { public abstract double Area(); public void Describe() => Console.WriteLine($"Площа: {Area().ToString()}"); } class Circle : Shape { double _r; public Circle(double r) { _r = r; } public override double Area() => Math.PI * _r * _r; } Shape s = new Circle(5); s.Describe();Що буде, якщо абстрактний клас успадковує інший абстрактний клас і не реалізовує всі його абстрактні методи? Хто тоді зобов'язаний реалізувати їх?
Як оголосити абстрактну властивість? Чим її синтаксис відрізняється від оголошення абстрактного методу?
У чому перевага абстрактного класу порівняно зі звичайним класом, у якого
virtual-методи мають «заглушку» (порожню реалізацію абоthrow new NotImplementedException())?Спроєктуйте ієрархію для системи сповіщень: абстрактний клас
Notifierз абстрактним методомSend(string message)і конкретним методомLog(string message). Реалізуйте два похідних класи —SmsNotifierіEmailNotifier. Напишіть метод, що приймаєNotifierі надсилає повідомлення.Чому не можна оголосити абстрактний метод у неабстрактному класі? Поясніть логіку цього обмеження.
4.7. Клас System.Object та його методи
Які чотири методи гарантовано є в кожному об'єкті C#? Від чого це походить?
Що виведе виклик
Console.WriteLine(patient), якщо класPatientне перевизначаєToString()? Що зміниться після перевизначення?Поясніть контракт рівності: яке правило між
EqualsіGetHashCodeобов'язково дотримуватися і чому його порушення призведе до помилок у словниках і множинах?Реалізуйте
EqualsіGetHashCodeдля класуPatient, де рівність визначається збігом поляRecordId(рядок). Що повертає вашEquals, коли передається об'єкт іншого типу?Чим відрізняється
person.GetType() == typeof(Patient)відperson is Patient? Наведіть приклад, коли вони дають різний результат.Чому метод
GetType()не можна перевизначити? Яку гарантію це забезпечує?Як перевизначити
ToString()так, щоб при порожньому імені поверталась базова реалізація (base.ToString()), а при заповненому — змістовний рядок? Напишіть повну реалізацію.У вас є
Dictionary<Patient, string>. Поясніть, чому без правильного перевизначенняEqualsіGetHashCodeдва об'єктиPatientз однаковимRecordIdбудуть вважатися різними ключами.
4.8. Узагальнення
Які дві ключові проблеми вирішують узагальнення (generics) порівняно з використанням
objectяк типу загального призначення?Що таке параметр типу і аргумент типу? Поясніть різницю на прикладі
class Box<T>таBox<int> b = new Box<int>().Що виведе наступний код?
class Container<T> { public static int Count = 0; public Container() { Count++; } } new Container<int>(); new Container<int>(); new Container<string>(); Console.WriteLine(Container<int>.Count); Console.WriteLine(Container<string>.Count);Що таке виведення типу (type inference)? Як компілятор визначає
Tу викликуSwap(ref a, ref b)без явного вказання<int>?Напишіть узагальнений метод
FindFirst<T>(T[] array, T target), що повертає індекс першого входження елемента або -1, якщо не знайдено. Чому для порівняння елементів не можна використати==без обмеження типу?Чому
List<string>не можна присвоїти зміннійList<object>, навіть якщоstringє нащадкомobject? Як ця проблема пов'язана з поняттям інваріантності generics у C#?Порівняйте
List<int>і старийArrayListз точки зору boxing/unboxing та типобезпеки. Чому перший кращий?Спроєктуйте узагальнений клас
Pair<T1, T2>, що зберігає два значення різних типів. Додайте методSwap(), що повертає новийPair<T2, T1>з переставленими значеннями. Продемонструйте використання.
4.9. Обмеження узагальнень
Навіщо потрібні обмеження (
where) у узагальнених класах і методах? Що дозволяє зробити обмеження, що неможливо без нього?Поясніть різницю між
where T : classіwhere T : struct. Що гарантує кожне з них?Що дозволяє обмеження
where T : new()? Чому без нього написатиnew T()у тілі узагальненого класу є помилкою компіляції?Чому
where T : structіwhere T : new()є несумісними? Поясніть логіку.Що виведе цей код?
class Printer<T> where T : IComparable<T> { public void PrintMax(T a, T b) { Console.WriteLine(a.CompareTo(b) >= 0 ? a : b); } } new Printer<int>().PrintMax(3, 7); new Printer<string>().PrintMax("яблуко", "банан");У чому різниця між
where T : SomeClassіwhere T : ISomeInterface? Яка перевага обмеження за інтерфейсом?Напишіть узагальнений метод
Max<T>(T a, T b) where T : IComparable<T>, що повертає більше з двох значень. Поясніть, чому без обмеження цей метод написати неможливо.Ви проєктуєте
Repository<T>, деTповинен бути reference type і мати конструктор без параметрів. Запишіть правильне оголошення класу з обмеженнями і поясніть кожне.
4.10. Наслідування узагальнених типів
Опишіть чотири варіанти відношення між параметром типу базового і похідного узагальнених класів. Наведіть синтаксис кожного.
Коли доцільно фіксувати тип параметра у похідному класі (
Child : Base<string>)? Що при цьому відбувається з гнучкістю похідного класу?Що виведе цей код?
class Base<T> { public T Value { get; } public Base(T v) { Value = v; } public override string ToString() => $"[{Value}]"; } class Child : Base<int> { public string Tag { get; } public Child(int v, string tag) : base(v) { Tag = tag; } public override string ToString() => $"{base.ToString()} — {Tag}"; } Child c = new Child(42, "тест"); Base<int> b = c; Console.WriteLine(c); Console.WriteLine(b);Якщо базовий клас
Base<T>має обмеженняwhere T : class, чи може похідний класChild<T> : Base<T>прибрати це обмеження? Чому?Ви маєте
AnnotatedRecord<T, TNote> : MedicalRecord<T>. Що означає такий підпис і яку гнучкість він надає?У чому різниця між
Child<T> : Base<T>іChild<T> : Base<int>? Де в кожному варіанті визначається типIdбазового класу?Спроєктуйте мінімальну ієрархію: абстрактний
Repository<T> where T : classз методамиAdd(T item)іGetAll(), та конкретнийPatientRepository : Repository<Patient>. Реалізуйте зберігання через масив фіксованого розміру.Порівняйте варіанти
FlexibleRecord<T> : MedicalRecord<T>(передача параметра) таInpatientRecord : MedicalRecord<string>(фіксація типу). Для яких задач підходить перший варіант, а для яких другий?