Підрозділ 6.Q
Питання для самоконтролю
Питання для самоконтролю — Розділ 6. Делегати, події та лямбди 6.1. Делегати 1. Що таке делегат у C ? Чим він принципово відрізняється від звичайної змінної типу int або string ? Яке місце займають делегати в к
Питання для самоконтролю — Розділ 6. Делегати, події та лямбди
6.1. Делегати
Що таке делегат у C#? Чим він принципово відрізняється від звичайної змінної типу
intабоstring? Яке місце займають делегати в концепції «методи як об'єкти першого класу»?Оголошено делегат
delegate int Calculate(int x, int y). Які з наведених методів відповідають цьому делегату, а які — ні? Поясніть причину для кожного:int Add(int a, int b) => a + b; double Add(int a, int b) => a + b; int Add(int a, int b, int c) => a + b + c; int Multiply(ref int a, int b) => a * b;Що виведе наступний код? Поясніть, яке значення повертається, якщо делегат з кількома методами у списку виклику повертає значення:
Calculate calc = M1; calc += M2; calc += M3; Console.WriteLine(calc(10, 2)); int M1(int x, int y) { Console.WriteLine("M1"); return x + y; } int M2(int x, int y) { Console.WriteLine("M2"); return x - y; } int M3(int x, int y) { Console.WriteLine("M3"); return x * y; } delegate int Calculate(int x, int y);Порівняйте два синтаксиси ініціалізації делегата:
Calculate calc = AddтаCalculate calc = new Calculate(Add). Чи є між ними функціональна різниця? В яких ситуаціях кожен з них зручніший?Поясніть патерн «зворотного виклику» (callback) через делегати. Чому жорстко вшитий
Console.WriteLineу метод бізнес-класу — це погане рішення, і як делегати вирішують цю проблему? Наведіть конкретний приклад.Напишіть метод
ApplyOperation(double[] values, Transform op), деTransform— делегатdelegate double Transform(double x), який застосовує операцію до кожного елемента масиву і повертає новий масив результатів. Викличте метод тричі з різними операціями.Що відбудеться при виклику делегата, якщо його список виклику порожній (делегат дорівнює
null)? Як безпечно викликати делегат, не отримуючиNullReferenceException? Покажіть два різних способи.Що таке анонімний метод? Чим він відрізняється від іменованого методу і де його доречно використовувати? Чи може анонімний метод не вказувати параметри, навіть якщо делегат їх приймає?
6.2. Лямбди
Що таке лямбда-вираз у C#? Назвіть основні складові синтаксису: лямбда-оператор, список параметрів, тіло. Яким типом даних є лямбда-вираз з точки зору системи типів C#?
Порівняйте три способи записати одну й ту саму логіку: іменований метод, анонімний метод і лямбда-вираз. Напишіть кожен варіант для делегата
delegate bool IsMatch(double value)з умовоюvalue > 6.1.Що виведе наступний код? Поясніть механізм автоматичного виведення типів параметрів лямбди:
AlertHandler alert = (name, msg) => Console.WriteLine($"[{name}]: {msg}"); alert("ICU", "Критичний стан"); delegate void AlertHandler(string department, string message);Коли в лямбді можна опустити дужки навколо параметра? Коли потрібен явний
return, а коли він автоматичний? Наведіть приклади обох варіантів.Чим лямбда-вираз, що повертає результат, відрізняється від лямбди без повернення? Напишіть лямбда-вираз для класифікації показника пульсу: якщо менше 60 — «Брадикардія», більше 100 — «Тахікардія», інакше — «Норма».
Що виведе наступний код? Поясніть, як і чому спрацьовує видалення лямбди через
-=:AlertChain log = () => Console.WriteLine("LOG"); AlertChain sms = () => Console.WriteLine("SMS"); AlertChain all = log; all += sms; all -= sms; all?.Invoke(); delegate void AlertChain();Поясніть концепцію «фабрики функцій»: метод повертає лямбда-вираз як результат. Яким є тип такого методу? Напишіть метод
CreateThresholdCheck(double min, double max), який повертає лямбду, що перевіряє, чи потрапляє значення в межі норми.Коли краще використовувати лямбда-вираз замість іменованого методу? Чи є ситуації, коли іменований метод переважніший? Аргументуйте свою відповідь.
6.3. Події
Що таке подія у C#? Чим подія принципово відрізняється від звичайного делегата? Які обмеження накладає ключове слово
eventна зовнішній код?У чому проблема жорстко вшитого
Console.WriteLineу бізнес-клас? Як події вирішують цю проблему? Поясніть ролі «видавця» (publisher) і «підписника» (subscriber) у патерні «видавець-підписник».Знайдіть помилку у наступному коді та поясніть, чому вона виникає:
class Button { public event Action Clicked; } class Program { static void Main() { Button btn = new Button(); btn.Clicked = () => Console.WriteLine("Натиснуто!"); // рядок 9 } }Поясніть, навіщо при виклику події використовувати
Notify?.Invoke(...)замість простоNotify(...). Що відбудеться в обох випадках, якщо жоден обробник не зареєстрований?Що таке аксесори
addіremoveдля подій? В яких ситуаціях їх варто визначати явно? Напишіть приклад події з аксесорами, що реєструють назву методу-обробника в консолі при підписці.Спроєктуйте клас
MedicalDeviceз подієюStatusChanged, яка передає обробнику два аргументи: попередній і новий статус пристрою. Визначте відповідний делегат і клас аргументів події.Поясніть антипатерн «витік пам'яті через події». За яких умов він виникає? Як його уникнути? Що означає правило «кожному
+=має відповідати-=»?Порівняйте три способи реєстрації обробника події: через іменований метод, анонімний метод і лямбду. Яка особливість виникає при спробі видалити анонімний обробник або лямбду через
-=?
6.4. Коваріантність та контраваріантність делегатів
Що таке коваріантність і контраваріантність делегатів? Поясніть простими словами: яка відмінність між ними і чого кожна стосується (параметри чи тип повернення)?
Є ієрархія:
EmailNotificationуспадковуєNotification. Оголошено делегатdelegate Notification Build(string text). Чи можна присвоїти йому методEmailNotification CreateEmail(string text)? Чому так або ні? Яке це явище?Поясніть логіку контраваріантності: делегат оголошує параметр
EmailNotification, а метод приймаєNotification. Чому компілятор вважає таке присвоєння безпечним? Обгрунтуйте через принцип «is-a».Що виведе наступний код? Поясніть, чому присвоєння у рядку 3 є допустимим або недопустимим:
// Ієрархія: Cat : Animal delegate Animal GetAnimal(); GetAnimal factory = () => new Cat(); // рядок 3 Animal a = factory();Навіщо в узагальнених делегатах потрібні ключові слова
outіin? Без них компілятор відмовляє у присвоєнні навіть для сумісних типів — чому?Оголошено
delegate T Transform<out T>(string input). Поясніть, що означаєoutу цьому контексті. Яке присвоєння тепер стає дозволеним:Transform<string>уTransform<object>чи навпаки?Спроєктуйте узагальнений делегат
delegate R Converter<in T, out R>(T input)для конвертації між медичними одиницями. Покажіть, як завдякиinіoutможна безпечно присвоїтиConverter<Animal, Cat>уConverter<Cat, Animal>— або навпаки, залежно від ієрархії.Порівняйте коваріантність і контраваріантність: що з ними «спільне», а що «протилежне»? В якому напрямі можливе присвоєння для кожної з форм? Наведіть приклад ієрархії і покажіть обидва напрями.
6.5. Делегати Action, Predicate та Func
Навіщо у .NET існують вбудовані делегати
Action,PredicateтаFunc? Чим вони зручніші порівняно з оголошенням власного делегата щоразу?В чому різниця між
Action<T>іFunc<T, bool>іPredicate<T>? Чи є функціональна різниця міжPredicate<double>іFunc<double, bool>? Коли кожен з них доречніший?Напишіть метод
Filter(int[] source, Predicate<int> condition), який повертає масив елементів, що задовольняють умові. Викличте його з лямбдою для вибору парних чисел, а потім для чисел більших за 50.Що виведе наступний код? Поясніть механізм замикання:
string ward = "Кардіологія"; int count = 0; Action<string> log = msg => { count++; Console.WriteLine($"[{ward}] #{count}: {msg}"); }; log("Пульс 145"); log("Тиск 180"); Console.WriteLine($"Всього: {count}");Поясніть, що таке замикання (closure). Які компоненти входять до замикання? Чому після виходу з зовнішньої функції захоплені змінні продовжують існувати?
Напишіть «фабрику предикатів» — метод
CreateRangeFilter(double min, double max), що повертаєPredicate<double>і перевіряє, чи потрапляє значення в заданий діапазон. Покажіть, що два різних виклики фабрики дають два незалежних предикати з різними порогами.Поясніть потенційну небезпеку замикань: яким чином захоплення великих об'єктів або довгоживучих об'єктів може призвести до проблем з пам'яттю? Назвіть принаймні одну ситуацію, де це стає реальною проблемою.
Порівняйте
Func<double, double, double>і власний делегатdelegate double Calculate(double a, double b). Коли варто оголошувати власний делегат, а коли досить вбудованогоFunc? Наведіть аргументи на користь кожного підходу.