[英]Use of null check in event handler
在檢查事件處理程序是否為 null 時,這是在每個線程的基礎上完成的嗎?
確保有人正在收聽事件是這樣完成的:
EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);
如果我在上面檢查 null 的地方添加代碼,那么我為什么需要 null 檢查(代碼取自此站點)。 我錯過了什么?
另外,事件和 GC 的規則是什么?
問題是如果沒有人訂閱該事件,它是 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(...);
}
}
這確保即使EventSeven
在OnSeven()
過程中發生變化,您也不會得到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);
在觸發事件處理程序之前檢查它始終是一個好習慣。 即使我最初“保證”自己始終設置它,我也會這樣做。 如果我稍后更改此設置,則不必檢查所有事件觸發。 因此,對於每個事件,我總是有一個伴隨的 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.