Использование нулевой проверки в обработчике событий - PullRequest
26 голосов
/ 23 марта 2009

При проверке, является ли обработчик события пустым, это делается для каждого потока?

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

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);

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

Кроме того, каково правило с событиями и GC?

Ответы [ 6 ]

51 голосов
/ 23 марта 2009

Проблема в том, что если никто не подписывается на событие, оно равно null. И вы не можете ссылаться на ноль. На ум приходят три подхода:

  • проверка на ноль (см. Ниже)
  • добавить обработчик "ничего не делать": public event EventHandler MyEvent = delegate {};
  • использовать метод расширения (см. Ниже)

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

protected virtual void OnMyEvent() {
    EventHandler handler = MyEvent;
    if(handler != null) handler(this, EventArgs.Empty);
}

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

    public static void SafeInvoke(this EventHandler handler, object sender)
    {
        if (handler != null) handler(sender, EventArgs.Empty);
    }
    public static void SafeInvoke<T>(this EventHandler<T> handler,
        object sender, T args) where T : EventArgs
    {
        if (handler != null) handler(sender, args);
    }

тогда вы можете позвонить:

MyEvent.SafeInvoke(this);

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

48 голосов
/ 23 марта 2009

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

public void OnSeven()
{
    DivBySevenHandler handler = EventSeven;
    if (handler != null)
    {
        handler(...);
    }
}

Это гарантирует, что даже если EventSeven изменится в течение OnSeven(), вы не получите NullReferenceException.

Но вы правы в том, что вам не нужна нулевая проверка, если у вас определенно есть подписанный обработчик. Это легко сделать в C # 2 с помощью обработчика «no-op»:

public event DivBySevenHandler EventSeven = delegate {};

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

С точки зрения сборки мусора, событие publisher заканчивается ссылкой на событие subscriber (то есть цель обработчика). Это проблема, только если издатель должен жить дольше, чем подписчик.

27 голосов
/ 01 сентября 2015

Я хочу добавить краткую информацию о синтаксисе C # 6.0:

Теперь можно заменить это:

var handler = EventSeven;

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

с этим:

handler?.Invoke(this, EventArgs.Empty);


Комбинируя его с членами с выражением тела , вы можете сократить следующий код:
protected virtual void OnMyEvent()
{
    EventHandler handler = MyEvent;
    handler?.Invoke(this, EventArgs.Empty);
}

до однострочного :

protected virtual void OnMyEvent() => MyEvent?.Invoke(this, EventArgs.Empty);


См. MSDN для получения дополнительной информации об условно-нулевом операторе
См. этот блог о членах с выраженным выражением
2 голосов
/ 23 марта 2009

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

private void OnEventSeven()
{
    var handler = EventSeven;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}

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

0 голосов
/ 26 марта 2012

Используя PostSharp , можно настроить скомпилированную сборку на этапе посткомпиляции. Это позволяет вам применять «аспекты» к коду, решая сквозные проблемы.

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

Это довольно просто:

[assembly: InitializeEventHandlers( AttributeTargetTypes = "Main.*" )]
namespace Main
{
   ...
}

Я подробно обсудил этот аспект в моем блоге . Если у вас есть PostSharp, вот аспект:

/// <summary>
///   Aspect which when applied on an assembly or class, initializes all the event handlers (<see cref="MulticastDelegate" />) members
///   in the class(es) with empty delegates to prevent <see cref="NullReferenceException" />'s.
/// </summary>
/// <author>Steven Jeuris</author>
[AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Event )]
[MulticastAttributeUsage( MulticastTargets.Event, AllowMultiple = false )]
[AspectTypeDependency( AspectDependencyAction.Commute, typeof( InitializeEventHandlersAttribute ) )]
[Serializable]
public class InitializeEventHandlersAttribute : EventLevelAspect
{
    [NonSerialized]
    Action<object> _addEmptyEventHandler;


    [OnMethodEntryAdvice, MethodPointcut( "SelectConstructors" )]
    public void OnConstructorEntry( MethodExecutionArgs args )
    {
        _addEmptyEventHandler( args.Instance );
    }

    // ReSharper disable UnusedMember.Local
    IEnumerable<ConstructorInfo> SelectConstructors( EventInfo target )
    {
        return target.DeclaringType.GetConstructors( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
    }
    // ReSharper restore UnusedMember.Local

    public override void RuntimeInitialize( EventInfo eventInfo )
    {
        base.RuntimeInitialize( eventInfo );

        // Construct a suitable empty event handler.
        MethodInfo delegateInfo = DelegateHelper.MethodInfoFromDelegateType( eventInfo.EventHandlerType );
        ParameterExpression[] parameters = delegateInfo.GetParameters().Select( p => Expression.Parameter( p.ParameterType ) ).ToArray();
        Delegate emptyDelegate
            = Expression.Lambda( eventInfo.EventHandlerType, Expression.Empty(), "EmptyDelegate", true, parameters ).Compile();

        // Create a delegate which adds the empty handler to an instance.
        _addEmptyEventHandler = instance => eventInfo.AddEventHandler( instance, emptyDelegate );
    }
}

... и вспомогательный метод, который он использует:

/// <summary>
///   The name of the Invoke method of a Delegate.
/// </summary>
const string InvokeMethod = "Invoke";


/// <summary>
///   Get method info for a specified delegate type.
/// </summary>
/// <param name = "delegateType">The delegate type to get info for.</param>
/// <returns>The method info for the given delegate type.</returns>
public static MethodInfo MethodInfoFromDelegateType( Type delegateType )
{
    Contract.Requires( delegateType.IsSubclassOf( typeof( MulticastDelegate ) ), "Given type should be a delegate." );

    return delegateType.GetMethod( InvokeMethod );
}
0 голосов
/ 23 марта 2009

Если вы имеете в виду это:

public static void OnEventSeven(DivBySevenEventArgs e)
    {
        if(EventSeven!=null)
            EventSeven(new object(),e);
    }    

кусок кода, тогда ответ:

Если никто не подписывается на обработчик событий EventSeven, вы получите исключение с нулевой ссылкой на EventSeven (new object (), e);

И правило:

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

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