← Все вопросы

Подписался на событие через += , но обработчик не вызывается — что не так?

Задан 15 месяцев назад702 просмотров2 ответа
10

Делаю простую кнопку-эмулятор и хочу через событие (event) уведомлять подписчиков о клике. Объявил событие, подписался через +=, но когда вызываю — ничего не происходит, обработчик молчит. Уже час смотрю и не вижу ошибку.

class Button
{
    public event Action Clicked;

    public void Click()
    {
        Clicked();   // тут падает или ничего
    }
}

class Program
{
    static void Main()
    {
        var btn = new Button();
        // забыл подписаться? или нет?
        btn.Click();
    }
}

Иногда вообще ловлю NullReferenceException на строке Clicked(). Что я делаю не так и как событие вообще связано с делегатами?

2 ответа

17
✓ Принятый ответ — помог автору

Тут две вещи. Во-первых, в Main ты не подписался на событие — поэтому ничего и не вызывается. Во-вторых, если подписчиков нет, поле события равно null, и вызов Clicked() падает с NullReferenceException.

Событие — это, по сути, обёртка над делегатом (Action — это делегат). Снаружи на него можно только подписаться (+=) и отписаться (-=), а вызывать его может только сам класс. Поэтому перед вызовом всегда проверяй на null. Удобнее всего через ?.Invoke:

class Button
{
    public event Action Clicked;

    public void Click()
    {
        Clicked?.Invoke();   // вызовется, только если есть подписчики
    }
}

static void Main()
{
    var btn = new Button();
    btn.Clicked += () => Console.WriteLine("Кнопка нажата!");
    btn.Click();   // теперь работает
}

Ключевое: подписка через += и проверка ?.Invoke() решают обе проблемы сразу.

7

Дополню про связь события и делегата, раз ты спросил. event — это не отдельная сущность, а модификатор поверх делегата. Запись public event Action Clicked; означает «поле-делегат Action, но снаружи доступны только += и -=».

Если убрать event, то снаружи можно было бы написать btn.Clicked = null или даже btn.Clicked() — то есть любой смог бы затереть подписчиков или дёрнуть событие. event это запрещает и оставляет управление только внутри класса. Поэтому события и делают через event, а не через публичный делегат.

Для «настоящих» событий по конвенции используют EventHandler / EventHandler<T>, но Action для учебных примеров вполне подходит.

Ваш ответ

Войдите, чтобы ответить на вопрос.
Поддержать проект