JavaRush /Курси /C# SELF /Лямбда-вирази для event

Лямбда-вирази для event і callback

C# SELF
Рівень 50 , Лекція 3
Відкрита

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);
}
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ