Подтвердить что ты не робот

Использование нулевой проверки в обработчике событий

При проверке, является ли обработчик событий нулевым, выполняется ли это по-потоку?

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

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);

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

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

4b9b3361

Ответ 1

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

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

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

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

public event DivBySevenHandler EventSeven = delegate {};

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

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

Ответ 2

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

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

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

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);

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

Ответ 3

Я выкапываю очень старое сообщение, но я просто хочу добавить небольшую информацию о С# 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 для получения дополнительной информации об операторе с нулевым условием
См. этот блог о членах, работающих с выражением

Ответ 4

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

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

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

Ответ 5

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

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

фрагмент кода, тогда ответ будет следующим:

Если никто не подписывается на обработчик событий EventSeven, вы получите исключение с помощью NULL-ссылки в "EventSeven (новый объект(), e);

И правило:

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

Ответ 6

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

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

Использование довольно просто:

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

I подробно обсудил этот вопрос в моем блоге. Если у вас есть 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 );
}