1. Вступ
У C# (та в .NET загалом) події — один зі способів, якими обʼєкти спілкуються між собою. Наприклад, у вас є кнопка, натискання на яку має щось запускати. Можна було б реалізувати обробку подій через оголошення окремого методу, потім цей метод явно підписати на подію… Але часто хочеться просто «описати реакцію прямо тут», не розпорошуючи логіку між різними частинами файлу.
Лямбда-вирази чудово розвʼязують це завдання — дозволяють буквально написати реакцію на подію в тому ж місці, де ви на неї підписуєтеся. Мінімум церемоній — максимум користі.
Те саме стосується зворотних викликів (callback): інколи ви передаєте в метод певну логіку для виконання «пізніше», наприклад, коли операція завершиться. І знову лямбда-вирази роблять це просто й наочно: уся потрібна логіка — саме тут, у місці виклику.
Події та callback-и: коротко про головне й де тут потрібні лямбди
Подія — це, по суті, «вивіска» на обʼєкті: «ось тут хтось може підписатися і виконати дію, коли щось станеться».
Callback (зворотний виклик) — це «я зараз даю вам функцію, а ви викличте її, коли буде потрібно». Дуже схоже на «залиште заявку — ми передзвонимо».
Коли ви знайомилися з делегатами та подіями, певно, бачили приблизно такий синтаксис:
button.Click += MyButtonClickHandler;
Де MyButtonClickHandler — спеціальний метод, який треба десь оголосити. Але коли логіка проста, хочеться обійтися парою рядків, не створюючи окремих методів заради однієї дії.
Тут і виходять на сцену лямбди:
button.Click += (sender, args) => { Console.WriteLine("Кнопку натиснули!"); };
Лаконічно, зрозуміло — і прямо тут!
2. Лямбди як обробники подій: від простого до цікавого
Проста форма: мигнула лампочка — щось вивели в консоль
Припустімо, маємо клас Button і хочемо реагувати на його подію Click.
// Уявімо собі таку кнопку:
public class Button
{
public event EventHandler? Click;
public void SimulateClick() // Для прикладу: "натискаємо" програмно
{
// Якщо хтось підписався — викликаємо обробники
Click?.Invoke(this, EventArgs.Empty);
}
}
Тепер використаємо цю кнопку:
var button = new Button();
// Підписуємося на подію кнопки за допомогою лямбди!
button.Click += (sender, args) =>
{
Console.WriteLine("Ура! Кнопку натиснули!");
};
button.SimulateClick(); // => Ура! Кнопку натиснули!
Пояснення:
— Усе, що після =>, — це тіло вашої «міні-функції», яку викличуть, коли станеться подія.
— Не потрібно перейматися життєвим циклом обробника: він живе стільки, скільки існує ваша підписка (+=). Важливо: якщо лямбда захоплює змінні ззовні і ви не відписуєтеся від події, це може призвести до витоків памʼяті.
Лямбда-обробник із захопленням змінних
int clickCount = 0;
button.Click += (sender, args) =>
{
clickCount++;
Console.WriteLine($"Кнопку натиснули вже {clickCount} раз(и)!");
};
button.SimulateClick(); // => Кнопку натиснули вже 1 раз(и)!
button.SimulateClick(); // => Кнопку натиснули вже 2 раз(и)!
Що відбувається:
Лямбда «захоплює» змінну clickCount ззовні й памʼятає її значення між викликами.
3. Застосування лямбда-виразів для callback-ів
Зворотний виклик часто використовують у ситуаціях, коли певному коду треба викликати вашу функцію пізніше — наприклад, після виконання якоїсь довгої роботи.
Передавання лямбди як callback
Припустімо, маємо метод, який приймає делегат або лямбду як аргумент:
void DoWork(Action callback)
{
Console.WriteLine("Починається робота...");
// Імітація роботи
System.Threading.Thread.Sleep(500); // (Так робити не варто в реальних застосунках, це блокувальна операція!)
callback(); // Після роботи викликаємо callback
}
DoWork(() => Console.WriteLine("Роботу завершено!"));
Результат:
Спочатку в консолі зʼявиться "Починається робота...", потім — "Роботу завершено!".
Можна передавати лямбди з параметрами:
void Calculate(int a, int b, Action<int> onResult)
{
int sum = a + b;
onResult(sum);
}
Calculate(5, 8, result => Console.WriteLine($"Результат: {result}"));
4. Типові помилки під час використання лямбд
Відсутність відписки від подій
Коли ви підписуєтеся на подію через лямбду, у вас немає «імені обробника», щоб потім легко відписатися:
button.Click += (s, e) => Console.WriteLine("...");
button.Click -= ??? // Як сюди передати вашу лямбду? Ніяк, якщо її не збережено.
Якщо дуже треба відписуватися — збережіть лямбду в змінну:
EventHandler handler = (s, e) => Console.WriteLine("...");
button.Click += handler;
// ...потім
button.Click -= handler;
Це важливо для довгоживучих обʼєктів: якщо не відписатися, посилання залишиться і, ймовірно, призведе до витоку памʼяті.
Захоплення змінних циклу
Класика: підписка на події всередині циклу:
for (int i = 0; i < 3; i++)
button.Click += (s, e) => Console.WriteLine(i); // Усі лямбди «пам’ятають» один і той самий i
У сучасних версіях C# цю поведінку виправлено для foreach, але для for інколи можуть бути нюанси — завжди будьте уважні із захопленими змінними!
Якщо потрібно, щоб кожна лямбда памʼятала власне значення, — створіть локальну копію:
for (int i = 0; i < 3; i++)
{
int localI = i;
button.Click += (s, e) => Console.WriteLine(localI);
}
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ