簡體   English   中英

在事件處理程序中使用 null 檢查

[英]Use of null check in event handler

在檢查事件處理程序是否為 null 時,這是在每個線程的基礎上完成的嗎?

確保有人正在收聽事件是這樣完成的:

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);

如果我在上面檢查 null 的地方添加代碼,那么我為什么需要 null 檢查(代碼取自此站點)。 我錯過了什么?

另外,事件和 GC 的規則是什么?

問題是如果沒有人訂閱該事件,它是 null。 而且您不能針對 null 調用。 三種方法躍入腦海:

  • 檢查 null(見下文)
  • 添加“什么都不做”處理程序: public event EventHandler MyEvent = delegate {};
  • 使用擴展方法(見下文)

在檢查 null 時,為了線程安全,理論上您必須首先捕獲委托引用(以防它在檢查和調用之間發生變化):

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

擴展方法具有不尋常的屬性,它們可以在 null 實例上調用......

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

它既是空安全的(通過檢查)又是線程安全的(通過只讀取一次引用)。

我真的不清楚你的意思是什么,但如果委托可能是 null,你需要在每個線程上單獨檢查。 通常你會這樣做:

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

這確保即使EventSevenOnSeven()過程中發生變化,您也不會得到NullReferenceException

但是你是對的,你不需要 null 檢查你是否確實有一個訂閱的處理程序。 這可以在 C# 2 中通過“無操作”處理程序輕松完成:

public event DivBySevenHandler EventSeven = delegate {};

另一方面,如果您可能從各個線程獲得訂閱,您可能需要某種鎖定以確保您擁有“最新”的處理程序集。 我的線程教程中有一個示例可以提供幫助 - 盡管通常我建議盡量避免需要它。

在垃圾回收方面,事件發布者最終會引用事件訂閱者(即處理程序的目標)。 如果發布者的壽命比訂閱者長,這只是一個問題。

我想 append 一些關於 C# 6.0-Syntax 的簡短信息:

現在可以替換它:

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
看到這個關於表達體成員的博客

在觸發事件處理程序之前檢查它始終是一個好習慣。 即使我最初“保證”自己始終設置它,我也會這樣做。 如果我稍后更改此設置,則不必檢查所有事件觸發。 因此,對於每個事件,我總是有一個伴隨的 OnXXX 方法,如下所示:

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

如果事件處理程序對您的 class 是公共的,這一點尤其重要,因為外部調用者可以隨意添加和刪除事件處理程序。

如果你是這個意思:

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

一段代碼,那么答案是:

如果沒有人訂閱“EventSeven”事件處理程序,那么您將在“EventSeven(new object(),e);”上獲得空引用異常;

和規則:

當訂閱者不想再接收事件時,訂閱者負責添加處理程序 (+=) 並刪除它 (-=)。 垃圾收集遵循默認規則,如果不再引用 object,則可以對其進行清理。

使用PostSharp可以在編譯后步驟中調整已編譯的程序集。 這允許您將“方面”應用於代碼,解決橫切關注點。

盡管 null 檢查或空委托初始化可能是一個非常小的問題,但我編寫了一個方面,通過向程序集中的所有事件添加一個空委托來解決它。

它的使用非常簡單:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM