Как правильно отменить регистрацию обработчика событий

В обзоре кода я наткнулся на этот (упрощенный) fragment кода, чтобы отменить регистрацию обработчика события:

Fire -= new MyDelegate(OnFire); 

Я думал, что это не отменяет регистрацию обработчика события, потому что он создает новый делегат, который никогда не регистрировался раньше. Но в поисках MSDN я нашел несколько примеров кода, которые используют эту идиому.

Поэтому я начал эксперимент:

 internal class Program { public delegate void MyDelegate(string msg); public static event MyDelegate Fire; private static void Main(string[] args) { Fire += new MyDelegate(OnFire); Fire += new MyDelegate(OnFire); Fire("Hello 1"); Fire -= new MyDelegate(OnFire); Fire("Hello 2"); Fire -= new MyDelegate(OnFire); Fire("Hello 3"); } private static void OnFire(string msg) { Console.WriteLine("OnFire: {0}", msg); } } 

К моему удивлению, произошло следующее:

  1. Fire("Hello 1"); произвело два сообщения, как и ожидалось.
  2. Fire("Hello 2"); произвел одно сообщение!
    Это убедило меня в том, что незарегистрированные new delegates работают!
  3. Fire("Hello 3"); бросил NullReferenceException .
    Отладка кода показала, что после отмены регистрации события Fire имеет значение null .

Я знаю, что для обработчиков событий и делегата компилятор генерирует много кода за сценой. Но я до сих пор не понимаю, почему мои рассуждения ошибочны.

Что мне не хватает?

Дополнительный вопрос: из-за того, что Fire имеет значение null когда нет зарегистрированных событий, я делаю вывод, что везде, где происходит событие, требуется проверка на null .

Внедрение компилятора C # по умолчанию добавления обработчика событий вызывает Delegate.Combine , при удалении вызова обработчика событий Delegate.Remove :

 Fire = (MyDelegate) Delegate.Remove(Fire, new MyDelegate(Program.OnFire)); 

Реализация Framework Delegate.Remove Framework не относится к MyDelegate объекту MyDelegate , но к методу, на который указывает делегат ( Program.OnFire ). Таким образом, совершенно безопасно создавать новый объект MyDelegate при MyDelegate подписки на существующий обработчик событий. Из-за этого компилятор C # позволяет вам использовать сокращенный синтаксис (который генерирует точно такой же код за кулисами) при добавлении / удалении обработчиков событий: вы можете опустить new MyDelegate часть new MyDelegate :

 Fire += OnFire; Fire -= OnFire; 

Когда последний делегат удален из обработчика события, Delegate.Remove возвращает значение null. Как вы выяснили, важно проверить событие против null до его повышения:

 MyDelegate handler = Fire; if (handler != null) handler("Hello 3"); 

Он назначается временной локальной переменной для защиты от возможного состояния гонки с отменой подписки на обработчики событий в других streamах. (См. Мое сообщение в блоге для получения подробной информации о безопасности streamов при назначении обработчика события локальной переменной). Еще один способ защитить эту проблему – создать пустой делегат, который всегда подписывается; в то время как это использует немного больше памяти, обработчик событий никогда не может быть нулевым (и код может быть проще):

 public static event MyDelegate Fire = delegate { }; 

Вы должны всегда проверять, нет ли у делегата никаких целей (его значение равно null), прежде чем запускать его. Как было сказано ранее, один из способов сделать это – подписаться с анонимным методом do-nothing, который не будет удален.

 public event MyDelegate Fire = delegate {}; 

Однако это просто взломать, чтобы избежать NullReferenceExceptions.

Просто просто набрав, является ли делегат нулевым до вызова, он не является streamобезопасным, так как другой stream может отменить регистрацию после проверки нулевого значения и сделать его нулевым при вызове. Существует и другое решение – скопировать делегата во временную переменную:

 public event MyDelegate Fire; public void FireEvent(string msg) { MyDelegate temp = Fire; if (temp != null) temp(msg); } 

К сожалению, компилятор JIT может оптимизировать код, устранить временную переменную и использовать оригинальный делегат. (согласно Juval Lowy – Компоненты программирования .NET)

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

 [MethodImpl(MethodImplOptions.NoInlining)] public void FireEvent(MyDelegate fire, string msg) { if (fire != null) fire(msg); } 

Обратите внимание, что без атрибута MethodImpl (NoInlining) компилятор JIT может встроить метод, делая его бесполезным. Поскольку delegates неизменяемы, эта реализация является streamобезопасной. Вы можете использовать этот метод как:

 FireEvent(Fire,"Hello 3"); 
  • Действие параметра , в котором T3 может быть необязательным
  • self.delegate = self; что в этом плохого?
  • Действительно ли необходим Delegate.EndInvoke ()?
  • iPhone - UIImagePickerControllerDelegate наследование
  • Что именно делает делегат в проекте xcode ios?
  • Делегат Func с переменной ref
  • когда & зачем использовать делегатов?
  • objective делегатов
  • Как я могу совместно использовать объект между UIViewControllers на iPhone?
  • Разница между Invoke и DynamicInvoke
  • Завершение сеанса StopWatch с помощью делегата или lambda?
  • Interesting Posts

    Проблемы с перезаписью (повторным сохранением) изображения, когда он был установлен как источник изображения

    Преобразование растровых изображений в один многостраничный TIFF-образ в .NET 2.0

    Рекомендации по использованию интерфейса Java. Неужели получатели и сеттеры в интерфейсе плохо?

    Копирование данных из одной базы данных SQLite в другую

    Как сохранить сборки ASP.NET в AppDomain в живых?

    Какой stream запускает код после ключевого слова `await`?

    Запись в файл с командой «find» в пакетном скрипте

    Объясните, какое правило gitignore игнорирует мой файл

    Является ли использование многих статических методов плохим?

    Файл карты GNU Linker, показывающий неожиданные адреса загрузки

    Можно ли удалить указатель, который указывает на выделенный массив, но не на начало его?

    Преобразование UTC в текущее время локали

    Работа над Canvas.clipPath (), которая больше не поддерживается в android

    Как установить iframe src в Angular 2 без исключения `unsafe value`?

    Что случилось с иностранными ключами?

    Давайте будем гением компьютера.