Запустить событие в C # без привязанных методов делегата? - PullRequest
5 голосов
/ 11 февраля 2011

Я только что столкнулся с ошибкой в ​​программе, которую я пишу, когда было сгенерировано исключение о том, что «ссылка на объект должна быть установлена ​​на экземпляр объекта».В ходе расследования я обнаружил, что это исключение возникло при попытке вызвать событие, НО к этому событию не было добавлено ни одного метода делегата.

Я хотел убедиться, что мое понимание верно, что вы как разработчикне должны запускать события без предварительной проверки, что событие не равно null ?Например:

if (this.MyEventThatIWantToFire != null)
{
    this.MyEventThatIWantToFire();
}

Заранее спасибо за совет / мысли.

Ответы [ 6 ]

10 голосов
/ 11 февраля 2011

Показанный вами шаблон нарушен в многопоточной среде. MyEventThatIWantToFire может стать нулевым после теста, но до вызова. Вот более безопасный подход:

EventHandler handler = MyEventThatIWantToFire;
if (handler != null)
{
    handler(...);
}

Обратите внимание, что если вы не используете какой-то барьер памяти, нет гарантии, что вы увидите последний набор подписчиков, даже игнорируя очевидное состояние гонки.

Но да, если вы не знаете , что это будет ненулевое значение, вам нужно выполнить проверку или использовать вспомогательный метод, чтобы выполнить проверку за вас.

Один из способов убедиться, что подписчик всегда есть, - это самостоятельно добавить абонента, который не работает, например,

public event EventHandler MyEventThatIWantToFire = delegate {};

Конечно, события не должны быть реализованы с помощью простых полей делегатов. Например, вы могли бы иметь событие, подкрепленное List<EventHandler> вместо этого, используя пустой список для начала. Это было бы довольно необычно.

Само событие никогда не бывает нулевым, потому что само событие - это просто пара методов (добавить / удалить). Смотрите мою статью о событиях и делегатах для более подробной информации.

4 голосов
/ 11 февраля 2011

Вы не должны делать это таким образом - если кто-то, где удалить слушателя между if и вызовом события, вы получите исключение. Рекомендуется использовать его с локальной переменной:

protected void RaiseEvent()
{
    var handler = Event;

    if(handler != null)
        handler(this, EventArgs.Empty);
}

Конечно, это имеет тот недостаток, что может вызывать прослушиватель, который уже был удален между созданием локальной переменной и вызовом обработчика.

3 голосов
/ 11 февраля 2011

Да, вы должны проверить его на ноль, но то, как вы это делаете, приводит к состоянию гонки.Вполне возможно, что событие может в конечном итоге стать нулевым, если кто-то отписывает последнего делегата между вашим «если» и вашим повышением.Принятый способ сделать это выглядит следующим образом:

var temp = this.MyEventThatIWantToFire;
if (temp != null) {
    this.temp(this, EventArgs.Empty);
}

или, альтернативно, убедитесь, что в списке вызовов всегда есть хотя бы один делегат, объявив ваше событие следующим образом:

public event EventHandler MyEventThatIWantToFire = delegate {};

Имеет смысл

1 голос
/ 11 февраля 2011

Да, ваше понимание верно.Вы должны проверить значение делегата перед его вызовом.

Большинство делегатов - какие события - являются "многоадресными" делегатами.Это означает, что делегат может управлять списком одного или нескольких методов, которые он будет вызывать при активации.

Когда делегат оценивает значение null, зарегистрированных методов нет;следовательно, звонить нечего.Если он оценивается не в значение null, зарегистрирован как минимум один метод.Вызов делегата - это инструкция для вызова всех зарегистрированных им методов.Методы вызываются не в гарантированном порядке.

Использование оператора сложения-присваивания (+=) или оператора сложения (+) добавляет метод в список методов делегата.Использование оператора назначения вычитания (-=) или оператора вычитания (-) удаляет метод из списка методов делегата.

Также обратите внимание, что вызываемые методы выполняются из потока вызывающего.Это важно, если вы используете события для обновления элементов управления вашего пользовательского интерфейса.В этой ситуации использование свойства InvokeRequired вашего элемента управления позволяет корректно обрабатывать вызовы между потоками (используя Invoke() или BeginInvoke()).

0 голосов
/ 11 февраля 2011

Да, вы должны проверить, не является ли событие пустым. В основном вы сталкиваетесь с защищенным методом, который выполняет проверку и вызывает событие или даже создает аргументы события. Примерно так:

public event EventHandler Foo;

protected void OnFoo() {
    if (Foo == null)
        return;

    Foo(this, new EventArgs());
}

Этот подход также позволяет вашим производным классам вызывать (или предотвращать вызов) событие.

0 голосов
/ 11 февраля 2011

Да, в C #, если у события нет делегатов, оно будет нулевым, поэтому необходимо проверить

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...