OOP Course
Сьогодні

Підрозділ 6.5

Делегати Action, Predicate та Func

Розглядає вбудовані делегати Action, Predicate і Func, а також замикання через локальні функції та лямбда-вирази.

6.5. Делегати Action, Predicate та Func

У .NET є кілька вбудованих узагальнених делегатів, які покривають переважну більшість сценаріїв використання делегатів без потреби оголошувати власні типи. Найбільш вживані з них — Action, Predicate та Func. Їх наявність у стандартній бібліотеці дозволяє писати компактний код: замість delegate void AlertHandler(string msg) достатньо написати Action<string>, і компілятор розуміє те ж саме.

Action

Делегат Action представляє дію, яка нічого не повертає (void). Він має кілька перевантажених версій залежно від кількості параметрів: від Action (без параметрів) до Action<T1, T2, …, T16> (до 16 параметрів):

public delegate void Action()
public delegate void Action<in T>(T obj)
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2)
// ... до Action<T1..T16>

Action зазвичай передається як параметр методу і описує реакцію на якусь подію. Наприклад, метод обробки показника пацієнта може приймати Action як параметр і виконати потрібну дію залежно від контексту:

Метод ProcessVitals не містить жодної логіки виведення — він лише викликає дію report, яку передає зовнішній код. Це дає гнучкість: в одному місці передаємо LogAlert, в іншому — LogNormal, або будь-яку лямбду.

Action без параметрів зручний для реєстрації простих зворотних викликів:

Predicate

Делегат Predicate<T> приймає один параметр типу T і повертає bool. Він описує умову — відповідає або не відповідає об'єкт певному критерію:

delegate bool Predicate<in T>(T obj);

Predicate використовується для фільтрації, перевірки, пошуку. Наприклад, перевіримо кілька показників пацієнта на відповідність нормі:

Predicate<T> можна передавати у методи як параметр фільтрації:

Func

Делегат Func повертає значення і може приймати параметри. Останній параметр типу завжди є типом повернення (позначається TResult):

Func<TResult>                          // без параметрів, повертає TResult
Func<T, TResult>                       // один параметр T, повертає TResult
Func<T1, T2, TResult>                  // два параметри, повертає TResult
// ... до Func<T1..T16, TResult>

Func часто використовується для обчислень і перетворень. Наприклад, набір функцій обробки медичних показників:

Func зручний також як параметр методу — для стратегій обчислення:

Вбудовані делегати: Action, Predicate, Func

Замикання

Замикання (closure) — це функція, яка «запам'ятовує» своє лексичне оточення навіть тоді, коли виконується поза областю видимості, де вона була створена. Якщо звичайна функція звертається лише до своїх параметрів і локальних змінних, то замикання також має доступ до змінних зовнішньої функції, у якій воно визначене.

Технічно замикання складається з трьох компонентів:

  • зовнішня функція — визначає область видимості і змінні (лексичне оточення)
  • лексичне оточення — змінні та параметри зовнішньої функції
  • вкладена функція — захоплює і використовує лексичне оточення

У C# замикання реалізуються через локальні функції та лямбда-вирази.

Замикання через локальні функції

Тут CreateAlertCounter повертає локальну функцію Increment. Після виходу з CreateAlertCounter змінна count більше не існує «звичайним чином» на стеку — але замикання зберігає її в купі (heap). Кожен виклик counter() читає і змінює саме цю захоплену змінну, тому лічильник зростає між викликами.

Замикання через лямбда-вирази

За допомогою лямбд можна скоротити визначення замикання:

Кожен виклик createCounter створює нове замикання з власним незалежним count. icuCounter і cardCounter — два окремих замикання, кожне з яких зберігає свій власний лічильник.

Застосування параметрів у замиканнях

До лексичного оточення замикання належать не лише локальні змінні, а й параметри зовнішньої функції. Це дозволяє створювати «фабрики функцій» — методи, що повертають налаштовані дії:

Метод CreateRangeCheck приймає два порогові значення і повертає лямбду, яка їх «запам'ятовує». glucoseCheck і cholesterolCheck — це два різних замикання, кожне з яких містить свої пороги. Замість передавати low і high при кожному виклику — вони «вбудовані» у функцію один раз при її створенні.

Механізм замикання: захоплення лексичного оточення

Скорочений запис через каррінг (ланцюг лямбд):

Замикання — потужний механізм, але він несе відповідальність: захоплена змінна живе у пам'яті доти, доки живе замикання. Якщо замикання зберігається довго (наприклад, підписане на подію, що ніколи не відписується), захоплені великі об'єкти не звільняться збирачем сміття. При роботі з замиканнями слід усвідомлювати, які саме змінні захоплюються і який час вони мають жити.

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