[.Net Concept]理解事件的宣告方式與用法




事件在.Net程式中算是滿常使用的,雖然很多人都已經會使用了,但就筆者的經驗來看,撰寫事件時有些良好的習慣往往被人給忽略。筆者相信越是簡單的東西越是難被一窺究竟,多半這些簡單的觀念都是淺嚐則止。有鑑於此,筆者將個人的使用經驗與個人的理解整理一下,筆記於此文。





首先以事件的宣告方式來看,我們一開始所學的事件宣告多半是要我們先行宣告出事件的委派,再由事件的委派去建出事件。




#region EventHandler
delegate void NameChangingEventHandler(object sender,EventArgs e);
delegate void NameChangedEventHandler(object sender,EventArgs e);
#endregion

#region Event
event NameChangingEventHandler NameChanging;
event NameChangedEventHandler NameChanged;
#endregion





這樣做可以運行的很好,程式的寫法也沒錯,但是我們所關心的是事件,事件的委派只是為了建立事件所加,並不是我們所關注的重點。如果今天在撰寫的類別有很多事件,事件的委派勢必會雖之增長,程式中會雜有許多不被我們所關注的重點,變得很難被閱讀與理解,也由於要為事件建立對應的事件委派,撰寫的程式量因此增多,開發效率就隨之下降,到最後可能會讓人因此排斥為類別加入適當的事件。




event EventHandler NameChanging;
event EventHandler NameChanged;





這點.Net BCL的開發人員也注意到了,.Net BCL中所提供的EventHandler與EventHandler<T>這兩個委派就是因此而生的,善用這兩個委派來建立事件,開發人員不再需要自行建立對應的事件委派,能避免自行建立過多不必要的委派,程式碼不僅可以精簡易懂,開發效率也能因此提升。





談到這邊,可能有人會質疑說那如果我要的事件有超過兩個參數,若不止只有sender跟e這兩個怎麼辦?內建的EventHandler不能Cover到這塊,還不是得建立對應的事件委派?其實並不該會有這樣的狀況發生。因為事件的參數能的話我們都應該讓他盡可能保持sender跟e這兩個。sender參數能夠讓我們知道觸發事件的物件實體,多半我們會帶入this將事件觸發本身的物件參考給帶過去,e參數則是提供我們所需的額外資訊,若事件有額外的資訊要告知,代表以內建的EventArgs當作e的型態是不夠的,此時應該是自行實作對應的EventArgs,決非是為事件加入sender與e以外的參數,這樣不僅事件處理常式的參數不一致外,也可能讓人分不出是方法還是事件處理常式。






#region Event
event EventHandler<FriendEventArgs> FriendAdded;
#endregion

#region Protected Method
protected void OnFriendAdded(FriendEventArgs e)
{
if (FriendAdded == null)
return;
FriendAdded(this, e);
}
#endregion

#region Public Method
public void AddFriend(Person friend)
{
Friends.Add(friend);
OnFriendAdded(new FriendEventArgs(friend.Name));
}
#endregion



class FriendEventArgs : EventArgs
{
#region Var
private String _name;
#endregion

#region Property
public String Name
{
get
{
if (_name == null)
return String.Empty;
return _name;
}
private set
{
_name = value;
}
}
#endregion

#region Constructor
public FriendEventArgs(string name)
{
this.Name = name;
}
#endregion
}





在事件的觸發方面,建議為每個事件都建立對應的Protected方法,以On + Event Name的方式命名,用以觸發特定的事件,這樣可以不用每個觸發點都去判斷事件是否繫結,若是繫結才觸發,程式能因此精簡好讀,也由於事件都是由此觸發方法所觸發,在除錯時這樣的寫法也可以讓我們很容易的從這方法向前查詢觸發事件的點在哪邊。




#region Event
event EventHandler NameChanging;
event EventHandler NameChanged;
#endregion

#region Protected Method
protected void OnNameChanging(EventArgs e)
{
if (NameChanging == null)
return;
NameChanging(this, e);
}

protected void OnNameChanged(EventArgs e)
{
if (NameChanged == null)
return;
NameChanged(this, e);
}
#endregion





這樣的概念在.Net BCL也可以看到。



image





最後一提事件在使用時還有一個很重要的觀念,就是事件盡可能要保持在同一執行緒去觸發,這觀念筆者在”[.NET Concept]盡量避免在另一個執行緒觸發事件或委派”這篇已有詳述,這邊就不對此多做著墨。