Ответ
Событие (event) в C# — это языковая конструкция, реализующая шаблон проектирования «Наблюдатель» (Observer) или «Издатель-Подписчик». Она позволяет объекту (издателю) уведомлять другие объекты (подписчиков) о возникновении какого-либо действия, не имея прямых ссылок на них.
Базовые компоненты:
- Делегат (тип события): Определяет сигнатуру метода, который могут вызывать подписчики (обычно
EventHandlerилиEventHandler<TEventArgs>). - Ключевое слово
event: Объявляет событие как член класса. Оно инкапсулирует список вызовов делегатов (список подписчиков). - Метод-инициатор: Вызывает событие, когда происходит соответствующее действие.
- Обработчики событий: Методы в классах-подписчиках, которые выполняются при срабатывании события.
Практический пример (UI-кнопка):
// 1. Издатель (Publisher)
public class Button
{
// 2. Объявление события. Используется стандартный делегат EventHandler.
public event EventHandler Clicked;
// 3. Метод, инициирующий событие (protected virtual — стандартный шаблон).
protected virtual void OnClicked()
{
// Сохраняем ссылку на делегат в локальную переменную для потокобезопасности.
EventHandler handler = Clicked;
// Если есть подписчики (handler не null), вызываем событие.
handler?.Invoke(this, EventArgs.Empty);
}
public void SimulateClick()
{
Console.WriteLine("Button was pressed.");
OnClicked(); // Запускаем уведомление подписчиков.
}
}
// 4. Подписчик (Subscriber)
public class Logger
{
public void LogButtonClick(object sender, EventArgs e)
{
Console.WriteLine($"Button clicked at {DateTime.Now}. Sender: {sender}");
}
}
// Использование:
var myButton = new Button();
var logger = new Logger();
// Подписка на событие (добавление обработчика в список вызовов).
myButton.Clicked += logger.LogButtonClick;
// Можно подписать несколько методов.
myButton.Clicked += (s, e) => Console.WriteLine("Anonymous handler fired!");
// Имитация клика — вызовутся ВСЕ подписанные обработчки.
myButton.SimulateClick();
// Вывод:
// Button was pressed.
// Button clicked at ... Sender: Button
// Anonymous handler fired!
// Отписка от события.
myButton.Clicked -= logger.LogButtonClick;
Ключевые особенности и best practices:
- Инкапсуляция: Вне класса-издателя событие можно только
+=(подписаться) и-=(отписаться), но нельзя=(перезаписать всех подписчиков) или вызватьInvoke(). - Потокобезопасность: Перед вызовом всегда копируйте событие в локальную переменную (как в
OnClicked()), чтобы избежать исключенияNullReferenceException, если подписчик отпишется между проверкой на null и вызовом. - Отписка: Чтобы избежать утечек памяти, объекты должны отписываться от событий, время жизни которых превышает время жизни самого объекта.
Ответ 18+ 🔞
А, ну вот, опять эти ваши события в C#! Слушай, как будто на собеседовании попал, блядь. Ну ладно, разжую, как для дебила, только не ори.
Событие (event) — это такая хитрая жопа, чтобы один объект мог орать на всю деревню «Эй, пацаны, я щас чихну!», а другие, кто подписался, могли сказать «Ага, бля, услышали, сейчас нос вытрем». По-умному это «Наблюдатель» называется, но кому эта хуйня нужна, кроме интервьюеров?
Из чего состоит эта кухня:
- Делегат: Как договорённость. Все, кто хочет услышать чих, должны уметь сморкаться одинаково. Обычно все используют стандартный сморкатель
EventHandler. - Слово
event: Это типа объявление: «Так, бля, тут будет чихание!». Оно прячет за собой список всех, кто уши навострил. - Метод, который орет «Апчхи!»: Вызывает событие, когда приспичило.
- Обработчики: Это и есть те самые методы сморкания у всех подписчиков.
Пример, чтобы совсем доходило, как кнопка в интерфейсе:
// 1. Это наш чихальщик (Издатель)
public class Button
{
// 2. Объявляем, что у нас есть событие «Кликнули». Все будут использовать стандартный договор `EventHandler`.
public event EventHandler Clicked;
// 3. Метод, который орет «Апчхи!» (стандартный защищённый шаблон, запомни его, дурачок)
protected virtual void OnClicked()
{
// Хитрость! Копируем ссылку в локальную переменную, чтобы если кто-то отпишется *прямо сейчас*, мы не вылетели с ошибкой.
EventHandler handler = Clicked;
// Если есть хоть один подписчик (не null), орем на всех.
handler?.Invoke(this, EventArgs.Empty);
}
public void SimulateClick()
{
Console.WriteLine("Кнопку нажали, ёпта.");
OnClicked(); // Кричим всем, кто слушает!
}
}
// 4. А это подписчик, который любит всё логировать
public class Logger
{
public void LogButtonClick(object sender, EventArgs e)
{
Console.WriteLine($"Кнопка кликнута в {DateTime.Now}. Кричал: {sender}");
}
}
// Как этим пользоваться:
var myButton = new Button();
var logger = new Logger();
// Подписываемся на чих. Логгер теперь будет сморкаться.
myButton.Clicked += logger.LogButtonClick;
// Можно и анонимно подписать, чтоб два раза не вставать.
myButton.Clicked += (s, e) => Console.WriteLine("Анонимный обработчик тоже сработал!");
// Симулируем клик — и понеслась хуйня по кочкам!
myButton.SimulateClick();
// На экране будет:
// Кнопку нажали, ёпта.
// Кнопка кликнута в ... Кричал: Button
// Анонимный обработчик тоже сработал!
// Чтобы не засорять память, отписываемся.
myButton.Clicked -= logger.LogButtonClick;
Важные фишки, чтобы не обосраться:
- Инкапсуляция, блядь: Снаружи класса ты можешь только
+=и-=. Нельзя взять и всем подписчикам сказать= nullили самому вызвать событие. Это хорошо. - Потокобезопасность — голова болит: Всегда копируй событие в локальную переменную перед вызовом, как в
OnClicked(). Иначе в многопоточке можно поймать исключение, если подписчик отпишется в самый неподходящий момент. - Отписывайся, мудень: Если объект-подписчик живёт меньше, чем объект-издатель события, и не отпишется — это утечка памяти, он будет висеть мёртвым грузом. Сборщик мусора его не заберёт, потому что на него есть ссылка из списка подписчиков. Так что не забывай про
-=.