[英]Raising C# events with an extension method - is it bad?
我們都熟悉C#事件聲明的恐怖。 為了確保線程安全, 標准是編寫類似以下內容的代碼 :
public event EventHandler SomethingHappened;
protected virtual void OnSomethingHappened(EventArgs e)
{
var handler = SomethingHappened;
if (handler != null)
handler(this, e);
}
最近,在該板上的另一個問題(我現在找不到)中,有人指出擴展方法可以在這種情況下很好地使用。 這是一種實現方法:
static public class EventExtensions
{
static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
var handler = @event;
if (handler != null)
handler(sender, e);
}
static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
where T : EventArgs
{
var handler = @event;
if (handler != null)
handler(sender, e);
}
}
使用這些擴展方法后,您需要聲明和引發事件的過程是這樣的:
public event EventHandler SomethingHappened;
void SomeMethod()
{
this.SomethingHappened.RaiseEvent(this, EventArgs.Empty);
}
我的問題:這是個好主意嗎? 我們是否沒有標准的On方法會丟失任何東西? (我注意到的一件事是,它不適用於具有顯式添加/刪除代碼的事件。)
它仍然可以用於具有顯式添加/刪除操作的事件-您只需要使用委托變量(或者,您已經存儲了委托)而不是事件名稱。
但是,有一種更簡單的方法可以使其成為線程安全的-使用無操作處理程序對其進行初始化:
public event EventHandler SomethingHappened = delegate {};
調用額外的委托對性能的影響可以忽略不計,並且可以確保使代碼更容易。
順便說一句,在擴展方法中,您不需要額外的局部變量-您可以這樣做:
static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
if (@event != null)
@event(sender, e);
}
static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
where T : EventArgs
{
if (@event != null)
@event(sender, e);
}
就我個人而言,我不會使用關鍵字作為參數名稱,但是它根本不會真正改變調用方,因此您可以按照自己的意願進行:)
編輯:至於“ OnXXX”方法:您打算從中派生您的類嗎? 我認為,大多數課程都應該密封。 如果這樣做 ,您是否希望那些派生類能夠引發該事件? 如果對這兩個問題的回答均為“否”,請不要打擾。 如果兩者的答案均為“是”,請執行:)
現在C#6在這里,有一種更緊湊,線程安全的方法來觸發事件:
SomethingHappened?.Invoke(this, e);
由於空條件運算符“?”,只有在為事件注冊了委托(即不為空)時才Invoke()
。
問題中要解決的“處理程序”代碼的線程問題在這里被繞開了,因為像在該代碼中一樣, SomethingHappened
僅被訪問一次,因此在測試和調用之間不可能將其設置為null。
這個答案也許與原始問題相切,但是對於那些尋求一種簡單方法引發事件的人來說卻是非常不切實際的。
[這里有個想法]
只需按照推薦的方式編寫一次代碼,然后完成即可。 這樣您就不會混淆同事查看代碼以為您做錯了什么嗎?
[與編寫事件處理程序相比,我閱讀了更多試圖找到編寫事件處理程序的方法的文章。]
代碼更少,可讀性更高。 我喜歡
如果您對性能不感興趣,則可以這樣聲明事件,以避免進行空檢查:
public event EventHandler SomethingHappened = delegate{};
通過將處理程序分配給局部變量並不能“確保”線程安全。 分配后,您的方法仍然可能被中斷。 例如,如果在中斷期間處置了用於監聽事件的類,則您正在處置的類中調用方法。
您可以避免使用null引用異常,但正如Jon Skeet和cristianlibardo在其答案中指出的那樣,可以采用更簡單的方法來做到這一點。
另一件事是,對於非密封類,OnFoo方法應該是虛擬的,我認為擴展方法是不可能的。
為了進一步解決上述問題,您可以防止自己的處理程序中的任何一個引發異常。 如果發生這種情況,則不會調用后續處理程序。
同樣,您可以對處理程序進行任務分配,以防止長時間運行的處理程序對通知后一個處理程序造成過多的延遲。 這也可以防止源線程被長時間運行的處理程序劫持。
public static class EventHandlerExtensions
{
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public static void Taskify(this EventHandler theEvent, object sender, EventArgs args)
{
Invoke(theEvent, sender, args, true);
}
public static void Taskify<T>(this EventHandler<T> theEvent, object sender, T args)
{
Invoke(theEvent, sender, args, true);
}
public static void InvokeSafely(this EventHandler theEvent, object sender, EventArgs args)
{
Invoke(theEvent, sender, args, false);
}
public static void InvokeSafely<T>(this EventHandler<T> theEvent, object sender, T args)
{
Invoke(theEvent, sender, args, false);
}
private static void Invoke(this EventHandler theEvent, object sender, EventArgs args, bool taskify)
{
if (theEvent == null)
return;
foreach (EventHandler handler in theEvent.GetInvocationList())
{
var action = new Action(() =>
{
try
{
handler(sender, args);
}
catch (Exception ex)
{
_log.Error(ex);
}
});
if (taskify)
Task.Run(action);
else
action();
}
}
private static void Invoke<T>(this EventHandler<T> theEvent, object sender, T args, bool taskify)
{
if (theEvent == null)
return;
foreach (EventHandler<T> handler in theEvent.GetInvocationList())
{
var action = new Action(() =>
{
try
{
handler(sender, args);
}
catch (Exception ex)
{
_log.Error(ex);
}
});
if (taskify)
Task.Run(action);
else
action();
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.