Проверка делегата на null

Я читал важную книгу C# 3.0 и мне интересно, является ли это хорошим способом проверить делегатов на null?:

class Thermostat
{
    public delegate void TemperatureChangeHandler ( float newTemperature );

    public TemperatureChangeHandler OnTemperatureChange { get; set; }

    float currentTemperature;

    public float CurrentTemperature
    {
        get { return this.currentTemperature; }
        set
        {
            if ( currentTemperature != value )
            {
                currentTemperature = value;

                TemperatureChangeHandler handler = OnTemperatureChange;

                if ( handler != null )
                {
                    handler ( value );
                }
            }
        }
    }
}

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

6 ответов


оригинальный (несколько неточный) ответ:

было много дискуссий по этому поводу.

короче говоря: вы не можете гарантировать, что обработчик будет действителен даже при выполнении этого шага copy/check for null/ execute.

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

вы также можете просто do:

if (OnTemperatureChange != null )
{
    OnTemperatureChange ( value );
}

и обрабатывать значение null ссылка исключения.

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

2014-07-10 обновления:

я уступаю Эрик Липперт.

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


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


во-первых, вы на самом деле не публиковать событие - Итак, на данный момент, ваш код в "группу риска" людей, полностью испортил. Должно быть:

public event TemperatureChangeHandler CurrentTemperatureChanged;

имя "CurrentTemperatureChanged" важно для привязки данных (существует соглашение, которое использует среда выполнения-учитывая свойство Foo, оно будет искать FooChanged). , IMO это должно быть просто регулярным EventHandler. Привязка данных будет искать EventHandler, но что более важно: вы фактически не дают никакой информации в случае, если абонент уже не может получить, просто посмотрев на obj.CurrentTemperature.

Я дам остальную часть ответа в терминах TemperatureChangeHandler, но я бы рекомендовал вам (снова) переключиться на EventHandler:

public event EventHandler CurrentTemperatureChanged;

подход:

TemperatureChangeHandler handler = CurrentTemperatureChanged;
if(handler != null) handler(value);

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

другой подход является методом расширения:

public static class TemperatureChangeExt {
    public static void SafeInvoke(this TemperatureChangeHandler handler,
             float newTemperature) {
        if (handler != null) handler(newTemperature);
    }
}

тогда в вашем классе, вы можете просто использовать:

        if (currentTemperature != value) {
            currentTemperature = value;
            CurrentTemperatureChanged.SafeInvoke(value);
        }

Если класс термостата не должен быть потокобезопасным, то да, приведенный выше код прекрасен - пока есть только один поток, обращающийся к этому экземпляру термостата, нет никакого способа для OnTemperatureChange стать незарегистрированным между тестом для null и вызовом события.

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

http://www.yoda.arachsys.com/csharp/events.html

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


используйте знак вопроса для условного доступа:

OnTemperatureChange?.Invoke();


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

class Thermostat
{
    public delegate void TemperatureChangeHandler ( float newTemperature );

    public TemperatureChangeHandler OnTemperatureChange { get; set; }

    float currentTemperature;

    public float CurrentTemperature
    {
        get { return this.currentTemperature; }
        set
        {
                if (currentTemperature != value)
                {
                        currentTemperature = value;

                        if (this.OnTemperatureChange != null )
                        {
                                this.OnTemperatureChange.Invoke( value );
                        }
                }
        }
    }
}