使用事件的常用步驟:
- 定義事件參數(shù);
- 事件源類型中聲明事件;
- 注冊(cè)處理事件的方法,即監(jiān)聽(tīng);
- 再需要是,觸發(fā)事件。
public class CustomEventArgs: System.EventArgs
{
public string EventData { get; private set; }
public CustomEventArgs(string eventData)
{
EventData = eventData;
}
}
public class CustomEventSource
{
public System.EventHandler<CustomEventArgs> CustomEvent;
public void DoWork()
{
// do other work
// 最初的寫(xiě)法
if(null != CustomEvent)
{
CustomEvent(this, new CustomEventArgs("event argument"));
}
// 有經(jīng)驗(yàn)的同事會(huì)告訴我們,需要這樣寫(xiě)
//var handler = CustomEvent;
//if(null != handler)
//{
// handler(this, new CustomEventArgs("event argument"));
//}
}
}
public class EventUsage
{
private CustomEventSource handlerDemo;
public void Init()
{
handlerDemo = new CustomEventSource();
handlerDemo.CustomEvent += HandleCustomEvent;
}
private void HandleCustomEvent(object sender, CustomEventArgs e)
{
Console.WriteLine($"event: {e.EventData}");
}
public void UnInit()
{
if(null != handlerDemo)
{
handlerDemo.CustomEvent -= HandleCustomEvent;
}
}
}
其實(shí)那會(huì)不是很理解為什么這么寫(xiě),然后他們就告訴我,這樣可以避免多線程使用時(shí)帶來(lái)的bug,而且不好查。就是當(dāng)前程序執(zhí)行完if語(yǔ)句之后,會(huì)被另一個(gè)線程打斷,并且另一個(gè)線程解除事件監(jiān)聽(tīng),也就是解除了事件訂閱,這樣事件處理程序成了null,這樣就引發(fā)了空引用的異常。所以會(huì)先賦值一個(gè),用賦值后的內(nèi)容去處理事件。原理是:該賦值會(huì)對(duì)賦值符號(hào)右邊的內(nèi)容做淺拷貝(創(chuàng)建新引用,并令其指向原來(lái)的事件處理程序),當(dāng)另一個(gè)線程注銷事件處理程序的時(shí)候,只會(huì)修改類實(shí)例中的handlerDemo字段,而不會(huì)把該處理程序同時(shí)從局部變量handler里面移走,這樣,handler中還保存著早前執(zhí)行淺拷貝時(shí)所記錄的事件訂閱者,這樣就不會(huì)出錯(cuò)了。
這樣的處理是線程安全的,但是會(huì)復(fù)雜一些,C#引入的null條件運(yùn)算符,可以解決這個(gè)問(wèn)題,即:
CustomEvent?.Invoke(this, new CustomEventArgs("event argument"));
現(xiàn)在編譯器也會(huì)智能提示我們這樣修改,這樣很方便,而且在語(yǔ)義上,與早期的if結(jié)構(gòu)類似,但區(qū)別在于?.運(yùn)算符左側(cè)內(nèi)容只會(huì)計(jì)算一次?,F(xiàn)在我已經(jīng)慢慢習(xí)慣這種使用方式了。