Настройка / удаление обработчиков событий в .Net

StackOverflow https://stackoverflow.com/questions/611492

Вопрос

Итак, я застрял с исправлением / поддержанием другого программного кода (blech)

Я твердо придерживаюсь этого правила "Если он не сломался, не чини его!" поэтому, несмотря на желание что-то менять каждый раз, когда я сталкиваюсь с ужасающим кодом, я ограничиваю себя изменением только абсолютного минимального объема кода, возможного для внесения необходимых исправлений.Но в некоторых случаях мне действительно нужно что-то понять, прежде чем пытаться следовать этому / изменять.

Я наткнулся здесь на этот небольшой фрагмент:

region.LineSelected = (x) => { };

И мне интересно, действительно ли это то же самое, что и это:

region.LineSelected = null;

Я хочу быть на 100% уверен в том, что делает первая строка, прежде чем я начну менять метод, в котором она используется.

Это было полезно?

Решение

Редактировать, основываясь на моем текущем мнении по этому вопросу

Это не одно и то же.Версия lambda добавляет обработчик события к пустому анонимному методу.Это позволяет другому коду свободно вызывать LineSelected(), не беспокоясь о том, равно ли оно null (т.е.не имея слушателей).

Например.

var lineSelected = this.LineSelected;

if (lineSelected != null)
{
    lineSelected(EventArgs.Empty);
}

Приведенный выше оператор может вызвать исключение NullReferenceException, если что-то отписывается от LineSelected в другом потоке после if, но до возникновения события. Присвоение выбранной строки временной переменной и последующее повышение, которое может вызвать прослушиватель событий без подписки. Назначение обработчика события локальной переменной является рекомендуемым методом обработки нулевых делегатов.

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

Другие советы

Я не могу думать ни о какой причине, чтобы иметь эту первую строку кода. Единственное, о чем я могу подумать, это при вызове события LineSelected, если у вас есть первая строка кода в классе, вам не нужно проверять, является ли событие LineSelected нулевым. то есть:

if (this.LineSelected != null)
{
   LineSelected(this,new EventArgs());
}

Вместо этого вы можете просто вызвать событие без нулевых проверок.

Однако со второй строкой кода вам нужно будет проверить наличие нулей.

Это не обработчик событий, это простой делегат. (Обработчик события должен быть изменен с помощью + = и - =, чтобы присоединить и отсоединить событие).

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

Это может сделать вызов кода более удобным, но его не следует путать с улучшением производительности (устраняя необходимость проверять наличие нуля). Как правило, накладные расходы при вызове метода делегата будут значительно выше, чем при нулевой проверке. Могут быть некоторые сценарии (например, если у делегата есть «реальная» реализация 99,99% времени), где отказ от нулевой проверки может улучшить производительность, но трудно представить сценарий, в котором крошечная разница в производительности может иметь значение. того, что стоило бы того, что также не потребовало бы полностью удалить вызов делегата в пользу чего-то более эффективного.

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

Если код вызова событий LineSelected не имеет надлежащей нулевой проверки, это вызовет исключение:

region.LineSelected = null;

/* no event handlers added to LineSelected */

class Region {
    void OnLineSelected() {
        // Null error!
        LineSelected();
    }
}

Однако, если к нему добавлен пустой обработчик сторонних эффектов, приведенный выше код будет работать нормально, даже если никто не добавляет обработчик к событию, потому что всегда будет этот пустой обработчик.

Если говорить о том, что сказали Ричард и Эндрю, то это эквивалент

region.LineSelected = delegate {};

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

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

Самый простой способ обнаружить разницу - это посмотреть код, который компилятор генерирует от вашего имени при использовании лямбда-синтаксиса. Этот код:

using System;

class Program
{
    static void Main()
    {
        Action<int> func0 = (x) => { };
        Action<int> func1 = null;
    }
}

Действительно компилируется в это:

internal class Program
{
    // Methods
    private static void Main()
    {
        Action<int> func0 = delegate (int x) {
        };
    }
}

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

Они не одинаковы, потому что установлен обработчик событий.

Допустим, класс, который предоставляет доступ к LineSelected, забыл:

if( LineSelected != null )
    LineSelected(...)

Если этот класс будет вызывать LineSelected и никто его не слушает, то он выдаст исключение NullReferenceException

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

var event = LineSelected;     если (событие! = ноль)         событие (...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top