Проверка делегата на 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
для записи рекомендуется, чтобы вы разрабатывали свои классы, чтобы не быть потокобезопасными, если безопасность потоков явно не требуется, поскольку это может значительно увеличить сложность вашего кода.
Я просто вижу немного рефакторинга, который можно было бы сделать, но в остальном он выглядит хорошо...
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 );
}
}
}
}
}