二、傳統的委托 接下來講一講方法參數。下麵以“餐館服務員為客戶下單”[2]的事件作為描述。一般對事件的做法分3個部分: 1. 方法參數 EventArgs,一般用於傳送數據。在本例場景中 2 . 觸發事件的對象 // 下單的事件是Customer對象擁有的,∴寫在Customer類當中 3 . 執行 ...
二、傳統的委托
接下來講一講方法參數。下麵以“餐館服務員為客戶下單”[2]的事件作為描述。一般對事件的做法分3個部分:
1. 方法參數 EventArgs,一般用於傳送數據。在本例場景中
public delegate void OrderEventHandler(Customer cus, OrderEventArgs e); public class OrderEventArgs : EventArgs // 習慣把xxEventArgs 繼承於C#自帶的EventArgs { public string DishName { get; set;} // 菜名 public string Size { get; set;} // 份量 }
2 . 觸發事件的對象
// 下單的事件是Customer對象擁有的,∴寫在Customer類當中
public class Customer { private OrderEventHandler orderEventHandler; public event OrderEventHandler Order { // event類型是用來操作“方法類”這個盒子的 add { this.orderEventHandler += value;} // add是事件處理器的添加器 remove { this.orderEventHandler -= value;} } public void ThinkForOrder () // 顧客下單 { if (this.orderEventHandler != null ) { orderEventArgs e = new orderEventArgs{ DishName = "Soup", Size = "Large"}; this.orderEventHandler.Invoke(this, e); // this指此類實例化的customer } } }
3 . 執行的方法
在主函數中,為customer對象的Order事件訂閱waiter.Action,客戶的下單,需要由服務員行動。
cus.Order += waiter.Action;
即有:
public class Waiter
{ public void Action(Customer cus, OrderEventArgs e) { ... } }
總結上文:
Main |
Customer cus = new Customer(); Waiter wai = new Waiter(); cus.Order += wai.Action; cus.ThinkForOrder(); Console.WriteLine("the customer will pay {0}.", cus.BillPrice); |
Customer |
事件對象對eventHandler方法類的訂閱 |
Waiter |
public class Waiter { internal void Action(Customer customer, OrderEventArgs e) // internal可改為public { Console.WriteLine("Waiter will serve Mr.{0} {1}." , customer.Name, e.DishName); customer.Bill += e.Price; } } |
三、小結
如果把事件寫成委托型欄位的話:
- 假設有一客人badGuy,並且badGuy.Order += waiter.Action;,那麼如果badGuy.Order.Invoke(),即會破壞參數e,或者參數customer。
例如,badGuy不給自己點菜,點到了customerA上,badGuy.Order.Invoke(customerA, e2);
事件:
- 使邏輯、對象關係更加安全,防止“借刀殺人”。
- 只能寫在+=或-=的左邊。避免了委托被直接invoke調用。
(委托欄位可能在public當中被濫用,所以微軟推出Event這種成員。)
- 本質:委托欄位的包裝器;
對委托欄位的訪問僅起限製作用,僅暴露add、remove事件處理器的功能。
- 參數:一個表示發送者,e表示發送的消息/數據/內容
- 規定:事件觸發必須由事件Foo擁有者自己去發送信息。
觸發事件的方法一般命名為:OnFoo,意為事出有因。
註意:OnFoo的訪問級別一定是protected,若為public又可“借刀殺人”了。
首尾呼應:
屬性不是欄位——很多時候,屬性是欄位的包裝器,保護欄位不被濫用。包裝器永遠不可能是包裝的東西。
註釋:
[1] 自《深入理解C#》(第3版)Jon Skeet 著 姚琪琳 譯
[2] 自劉猛鐵的C#學習視頻