OOP Course
Сьогодні

Підрозділ 4.Q

Питання для самоконтролю

Питання для самоконтролю — Розділ 4. Об'єктно орієнтоване програмування 4.1. Успадкування 1. Що таке відношення is a ? Чим воно відрізняється від відношення has a ? Наведіть приклад неправильного застосування у

Питання для самоконтролю — Розділ 4. Об'єктно-орієнтоване програмування

4.1. Успадкування

  1. Що таке відношення is-a? Чим воно відрізняється від відношення has-a? Наведіть приклад неправильного застосування успадкування там, де слід використовувати композицію.

  2. Поясніть синтаксис : base(...) у конструкторі похідного класу. Що буде, якщо базовий клас має лише параметризований конструктор, а похідний не викличе base(...)?

  3. Що виведе цей код і в якому порядку?

    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();
  4. Чим відрізняються модифікатори protected та private з точки зору ієрархії успадкування? Коли доцільно використовувати protected?

  5. Що таке sealed клас? Чому у стандартній бібліотеці .NET клас String є sealed? Наведіть приклад ситуації, де sealed архітектурно виправданий.

  6. Чи передаються конструктори при успадкуванні? Що станеться, якщо базовий клас не має конструктора без параметрів і похідний клас не визначає власний конструктор?

  7. Спроєктуйте невелику ієрархію для транспортних засобів: базовий клас Vehicle з властивостями Make і Speed, та два похідних — CarNumberOfDoors) і TruckLoadCapacity). Всі класи мають конструктори, що використовують base(...).

  8. Від якого класу неявно успадковують усі класи в C#? Які методи це надає кожному об'єкту?


4.2. Перетворення типів

  1. Поясніть різницю між upcasting і downcasting. Яке з них безпечне і виконується неявно, а яке несе ризик і вимагає явного синтаксису?

  2. Що виведе цей код?

    Patient patient = new Patient("Іван", 45, "Грип");
    Person  person  = patient;
    Console.WriteLine(person.GetType().Name);

    Поясніть: чи є person і patient різними об'єктами у пам'яті?

  3. Яку помилку спричинить наступний код і коли саме — під час компіляції чи виконання?

    Person person = new Person("Сергій", 52);
    Patient patient = (Patient)person;
  4. У чому перевага оператора as перед явним приведенням (Patient)person? Яке значення повертає as у разі невдачі?

  5. Що робить вираз person is Patient patient (C# 7+)? Чим він зручніший за комбінацію окремої перевірки типу і явного приведення?

  6. У масиві Person[] зберігаються об'єкти типів Patient, Doctor і Person. Напишіть цикл з switch (pattern matching), що обробляє кожен тип окремо. Чому порядок гілок switch важливий?

  7. Порівняйте GetType() == typeof(Patient) та obj is Patient. В якому випадку кожен підхід поверне true, а в якому false?

  8. Вас попросили реалізувати метод PrintDetails(Person p), що виводить специфічну для кожного типу інформацію — Diagnosis для Patient, Specialization для Doctor. Реалізуйте його двома способами: через is-перевірку та через switch з pattern matching. Порівняйте читабельність.


4.3. Віртуальні методи та властивості

  1. Що означає ключове слово virtual у оголошенні методу? Що буде, якщо похідний клас не перевизначає віртуальний метод?

  2. Що таке поліморфізм у контексті віртуальних методів? Поясніть, чому наступний цикл виводить різний текст для кожного елемента масиву:

    Person[] staff = { new Person("A",30), new Patient("Б",45,"Грип"), new Doctor("В",38,"Кардіо") };
    foreach (Person p in staff)
        p.Print();
  3. Що таке пізнє зв'язування (late binding / dynamic dispatch)? Коли саме — під час компіляції чи виконання — вирішується, яка реалізація методу буде викликана?

  4. Що виведе цей код?

    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();
  5. Як можна перевизначити властивість у похідному класі? Чи зобов'язані збігатися accessor'и (get/set) у базовому та похідному класі?

  6. Що означає sealed override? Яку проблему він вирішує і коли виправданий?

  7. Спроєктуйте клас Shape з віртуальним методом Area(), що повертає double. Реалізуйте похідні класи Circle (з радіусом) і Rectangle (з шириною і висотою). Напишіть метод, що приймає Shape[] і виводить площу кожної фігури.

  8. Порівняйте два підходи до зміни поведінки в похідному класі: виклик base.Print() перед власним кодом (розширення) та повна заміна реалізації без виклику base. Коли доцільний кожен підхід?


4.4. Приховування методів та властивостей

  1. Що таке приховування методу в C#? Яке ключове слово використовується і що станеться, якщо його не вказати?

  2. Що виведе наступний код?

    class A { public void Print() => Console.WriteLine("A"); }
    class B : A { public new void Print() => Console.WriteLine("B"); }
    A obj = new B();
    obj.Print();

    Поясніть результат.

  3. Коли використання new (приховування) є єдиним або найкращим варіантом замість override (перевизначення)?

  4. Чим синтаксично відрізняється приховування властивості від перевизначення? Як у прихованій властивості звернутися до оригінальної базової реалізації?

  5. Чи можна приховати 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);
  6. Поясніть ситуацію: є бібліотека з класом Logger, у якого метод Write(string msg) не є virtual. Ви хочете додати до нього форматування і створили MyLogger : Logger. Як реалізувати власну версію Write? Які обмеження вашого рішення?

  7. Знайдіть потенційну проблему в наступному коді:

    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. Відмінність перевизначення та приховування методів

  1. Сформулюйте у двох реченнях ключову відмінність між override і new з точки зору зв'язування (binding). Що вирішує виклик у кожному випадку?

  2. Що таке таблиця віртуальних методів (VMT)? Як вона формується для класу і як використовується при виклику методу?

  3. Заповніть таблицю: що виведуть такі виклики?

    // 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();   // ?
  4. Чому виклик віртуального методу дещо повільніший, ніж невіртуального? За яких умов ця різниця суттєва?

  5. Якщо потрібно, щоб колекція List<Person> при переборі викликала правильну реалізацію Print() для кожного об'єкту, яку техніку слід використовувати — override чи new? Обґрунтуйте.

  6. Розробник написав такий код і дивується, чому 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();

    Поясніть, у чому помилка і як її виправити.

  7. Порівняйте у вигляді таблиці override та new за критеріями: зв'язування, що визначає виклик (тип змінної / тип об'єкта), чи потрібен virtual у базовому, VMT-поведінка.


4.6. Абстрактні класи та члени класів

  1. Чому не можна створити екземпляр абстрактного класу? Яку роль він відіграє в ієрархії?

  2. Порівняйте abstract метод і virtual метод за наявністю реалізації у базовому класі та зобов'язаннями похідних класів.

  3. Що виведе цей код або яку помилку він спричинить?

    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();
  4. Що буде, якщо абстрактний клас успадковує інший абстрактний клас і не реалізовує всі його абстрактні методи? Хто тоді зобов'язаний реалізувати їх?

  5. Як оголосити абстрактну властивість? Чим її синтаксис відрізняється від оголошення абстрактного методу?

  6. У чому перевага абстрактного класу порівняно зі звичайним класом, у якого virtual-методи мають «заглушку» (порожню реалізацію або throw new NotImplementedException())?

  7. Спроєктуйте ієрархію для системи сповіщень: абстрактний клас Notifier з абстрактним методом Send(string message) і конкретним методом Log(string message). Реалізуйте два похідних класи — SmsNotifier і EmailNotifier. Напишіть метод, що приймає Notifier і надсилає повідомлення.

  8. Чому не можна оголосити абстрактний метод у неабстрактному класі? Поясніть логіку цього обмеження.


4.7. Клас System.Object та його методи

  1. Які чотири методи гарантовано є в кожному об'єкті C#? Від чого це походить?

  2. Що виведе виклик Console.WriteLine(patient), якщо клас Patient не перевизначає ToString()? Що зміниться після перевизначення?

  3. Поясніть контракт рівності: яке правило між Equals і GetHashCode обов'язково дотримуватися і чому його порушення призведе до помилок у словниках і множинах?

  4. Реалізуйте Equals і GetHashCode для класу Patient, де рівність визначається збігом поля RecordId (рядок). Що повертає ваш Equals, коли передається об'єкт іншого типу?

  5. Чим відрізняється person.GetType() == typeof(Patient) від person is Patient? Наведіть приклад, коли вони дають різний результат.

  6. Чому метод GetType() не можна перевизначити? Яку гарантію це забезпечує?

  7. Як перевизначити ToString() так, щоб при порожньому імені поверталась базова реалізація (base.ToString()), а при заповненому — змістовний рядок? Напишіть повну реалізацію.

  8. У вас є Dictionary<Patient, string>. Поясніть, чому без правильного перевизначення Equals і GetHashCode два об'єкти Patient з однаковим RecordId будуть вважатися різними ключами.


4.8. Узагальнення

  1. Які дві ключові проблеми вирішують узагальнення (generics) порівняно з використанням object як типу загального призначення?

  2. Що таке параметр типу і аргумент типу? Поясніть різницю на прикладі class Box<T> та Box<int> b = new Box<int>().

  3. Що виведе наступний код?

    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);
  4. Що таке виведення типу (type inference)? Як компілятор визначає T у виклику Swap(ref a, ref b) без явного вказання <int>?

  5. Напишіть узагальнений метод FindFirst<T>(T[] array, T target), що повертає індекс першого входження елемента або -1, якщо не знайдено. Чому для порівняння елементів не можна використати == без обмеження типу?

  6. Чому List<string> не можна присвоїти змінній List<object>, навіть якщо string є нащадком object? Як ця проблема пов'язана з поняттям інваріантності generics у C#?

  7. Порівняйте List<int> і старий ArrayList з точки зору boxing/unboxing та типобезпеки. Чому перший кращий?

  8. Спроєктуйте узагальнений клас Pair<T1, T2>, що зберігає два значення різних типів. Додайте метод Swap(), що повертає новий Pair<T2, T1> з переставленими значеннями. Продемонструйте використання.


4.9. Обмеження узагальнень

  1. Навіщо потрібні обмеження (where) у узагальнених класах і методах? Що дозволяє зробити обмеження, що неможливо без нього?

  2. Поясніть різницю між where T : class і where T : struct. Що гарантує кожне з них?

  3. Що дозволяє обмеження where T : new()? Чому без нього написати new T() у тілі узагальненого класу є помилкою компіляції?

  4. Чому where T : struct і where T : new() є несумісними? Поясніть логіку.

  5. Що виведе цей код?

    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("яблуко", "банан");
  6. У чому різниця між where T : SomeClass і where T : ISomeInterface? Яка перевага обмеження за інтерфейсом?

  7. Напишіть узагальнений метод Max<T>(T a, T b) where T : IComparable<T>, що повертає більше з двох значень. Поясніть, чому без обмеження цей метод написати неможливо.

  8. Ви проєктуєте Repository<T>, де T повинен бути reference type і мати конструктор без параметрів. Запишіть правильне оголошення класу з обмеженнями і поясніть кожне.


4.10. Наслідування узагальнених типів

  1. Опишіть чотири варіанти відношення між параметром типу базового і похідного узагальнених класів. Наведіть синтаксис кожного.

  2. Коли доцільно фіксувати тип параметра у похідному класі (Child : Base<string>)? Що при цьому відбувається з гнучкістю похідного класу?

  3. Що виведе цей код?

    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);
  4. Якщо базовий клас Base<T> має обмеження where T : class, чи може похідний клас Child<T> : Base<T> прибрати це обмеження? Чому?

  5. Ви маєте AnnotatedRecord<T, TNote> : MedicalRecord<T>. Що означає такий підпис і яку гнучкість він надає?

  6. У чому різниця між Child<T> : Base<T> і Child<T> : Base<int>? Де в кожному варіанті визначається тип Id базового класу?

  7. Спроєктуйте мінімальну ієрархію: абстрактний Repository<T> where T : class з методами Add(T item) і GetAll(), та конкретний PatientRepository : Repository<Patient>. Реалізуйте зберігання через масив фіксованого розміру.

  8. Порівняйте варіанти FlexibleRecord<T> : MedicalRecord<T> (передача параметра) та InpatientRecord : MedicalRecord<string> (фіксація типу). Для яких задач підходить перший варіант, а для яких другий?

Розроблено Tomka Yurii · © 2026 ·