C# 事件與委託
1. 最近重溫c# 基礎, 看到微軟MVP劉鐵錳老師的視訊,其中委託和事件,講的讓我受益匪淺,所以記錄下學習心得。
2. 委託(delegate):
2.1 在c# 中,委託是一種類(class), 由於普通類(class)是一種資料型別,所以委託(delegate) 也是一種資料型別,它與類同級。
只是,由於它比較特殊,宣告的方式與一般類不同,根據劉鐵錳老師視訊,這樣子宣告方式主要是為了照顧可讀性和c/c++ 傳統。
2.2 宣告委託的位置 (避免宣告巢狀型別)
在我以前,使用Wimform 程式設計時,一般在多執行緒訪問UI執行緒時,主要使用委託,但是由於對委託理解不夠深入(delegate與class 同級),一般定義委託的位置都是類裡面。形成巢狀型別。
根據教程中提到,委託也是一種類,所以,宣告委託時,儘量與類的宣告同級。
2.3 在c#中的委託Action 和 Func<T> 泛型。
2.4 委託兩種呼叫方式, 如:
public delegate void Action(int x, int y);
Action action = new Action();
2.4.1 同步呼叫:action.Invoke(1, 2); 或者 action(1, 2);
2.4.2 非同步呼叫: action.BeginInvoke(1, 2);
3. 事件
3.1. 初步瞭解事件:
(1)定義: 單詞Event , 中文譯為“事件”,能夠發生的事情。
(2)角色: 是使用的物件,或者類,具備通知能力的成員。 棣屬於類或物件,不能脫離類或物件。
(中文翻譯) 事件(Event) 是一種能使物件或者類能夠提供通知的成員。
(英文原版) An event is a member that enables an object or class to provide notifications.
(3) 使用: 使用者物件或者類間的動作協調與資訊傳遞 (即訊息推送)。
原理: 事件模型 (Event model)
其中, a. 事件訂閱者,事件訊息的接受者, 事件的響應者,事件的處理者,被事件所通知的物件, 這5個都是指同一個東西。
b. 事件訊息, 事件資訊,事件資料,事件引數, 這4個都是指同一個東西。
3.2 事件的應用
(1) 事件模型的5個組成部分:
a. 事件擁有者(event source, 類或物件)
b. 事件成員( event, 成員)
c. 事件響應者(event subscriber, 物件)
d. 事件處理器(event handler, 成員) ----- 本質上是一個回撥的成員方法
e. 事件訂閱 --- 把事件處理器 與事件關聯一起,本質是一種以委託為基礎的“約定”。
注意:
a1. 事件處理器是方法成員,
a2. 掛接事件處理器的時候,可以使用委託例項(委託本質上一種類) ,也可以直接使用方法名,這是個語法糖。
a3. 事件處理器對事件的訂閱不是隨意的,編譯器會匹配與是否由宣告事件時所使用的委託型別(再次說明委託是一個類)來檢測。
a4. 事件可以同步呼叫(Event.Invoke),或者非同步呼叫 (Event.BeginInvoke).
有這幾種 情況,
4. 自定義事件
4.1 定義事件資訊型別,按照.Net 規定,事件資訊的命名,以事件 + EventArgs 字尾,並繼承EventArgs
public class OrderEventArgs:EventArgs
{
public String DishName { get; set; }
public String Size { get; set; }
}
4.2定義事件擁有者和委託:
根據.Net 的規定,如果委託,是為了宣告的某個事件而準備的,需要在委託後面增加EventHandler 字尾。
/// <summary>
/// 使用EventHandler 用意:
/// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
/// 2. 表明這個委託是用來宣告事件,約束事件處理器
/// 3. 委託示例,儲存事件處理器
/// </summary>
/// <param name="customer"></param>
/// <param name="args"></param>
public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);
/// <summary>
/// 事件擁有者
/// </summary>
public class Customer
{
}
/// 使用EventHandler 用意:
/// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
/// 2. 表明這個委託是用來宣告事件,約束事件處理器
/// 3. 委託示例,儲存事件處理器
委託也是一種類,所以,宣告委託時,儘量與類的宣告同級,避免巢狀。
4.3 定義事件
由於在上面提到,事件是使用的物件,或者類,具備通知能力的成員。 棣屬於類或物件,不能脫離類或物件。
因此,需要把事件定義到事件擁有者上面,並且使用上面定義的委託宣告來約束事件。
/// <summary>
/// 使用EventHandler 用意:
/// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
/// 2. 表明這個委託是用來宣告事件,約束事件處理器
/// 3. 委託示例,儲存事件處理器
/// </summary>
/// <param name="customer"></param>
/// <param name="args"></param>
public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);
/// <summary>
/// 事件擁有者
/// </summary>
public class Customer
{
//建立儲存事件處理的委託
private OrderEventHandler orderEventHandler;
//建立委託約束的事件 -- 事件最基本的建立放方式,
public event OrderEventHandler Order
{
add //新增器
{
this.orderEventHandler += value;
}
remove //移除器
{
this.orderEventHandler -= value;
}
}
}
4.4 定義事件響應者和事件處理器
/// <summary>
/// 事件響應者
/// </summary>
public class Waiter
{
/// <summary>
/// 事件處理器
/// </summary>
/// <param name="customer"></param>
/// <param name="args"></param>
public void EventAction(Customer customer, OrderEventArgs e)
{
Console.WriteLine("I will serve you the dish - {0}", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
customer.Bill = price * 0.5;
break;
case "large":
customer.Bill = price * 1.5;
break;
}
}
}
上面也提到,事件處理器(event handler, 成員) ----- 本質上是一個回撥的成員方法
4.5 在事件擁有者中,增加觸發方法
/// <summary>
/// 使用EventHandler 用意:
/// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
/// 2. 表明這個委託是用來宣告事件,約束事件處理器
/// 3. 委託示例,儲存事件處理器
/// </summary>
/// <param name="customer"></param>
/// <param name="args"></param>
public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);
/// <summary>
/// 事件擁有者
/// </summary>
public class Customer
{
//建立儲存事件處理的委託,但是沒有分配
private OrderEventHandler orderEventHandler;
//建立委託約束的事件 -- 事件最基本的建立放方式,
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}
public double Bill { get; set; }
public void PlayTheBill()
{
Console.WriteLine("I will pay ¥{0}", this.Bill);
}
public void Walkln()
{
Console.WriteLine("Walk into the restaurant");
}
public void SitDown()
{
Console.WriteLine("Sit down");
}
//事件觸發,都是由事件擁有者,內部一些邏輯方法觸發, 然後呼叫事件處理器
public void Think()
{
for (int ik = 0; ik < 5; ik++)
{
Console.WriteLine("Let me think 。。。。。");
Thread.Sleep(1000);
}
//觸發事件處理器,需要判斷是否增註冊了事件處理
if (this.orderEventHandler != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "KongPao Chicken";
e.Size = "large";
this.orderEventHandler.Invoke(this, e);
//函式指標用法
//this.orderEventHandler(this, e);
}
}
public void Action()
{
Console.WriteLine("Please input enter: ");
Console.ReadLine();
this.Walkln();
this.SitDown();
this.Think();
}
}
5. 關於事件宣告
5.1 事件的宣告:
a. 完整宣告, 上面就是完整宣告,
(1) 在事件擁有者類中先定義委託,
private OrderEventHandler orderEventHandler;
(2)使用委託約束定義事件
//建立委託約束的事件 -- 事件最基本的建立放方式,
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}
b. 簡略宣告(欄位式宣告,field-like)
public eventOrderEventHandler Order;
//訪問許可權, event 建立事件, OrderEventHandler (哪種委託型別,作為事件的約束),Order 事件名字
5.2 有了委託欄位/屬性, 為什麼還要事件?
為了程式的邏輯更加有道理,更加安全。
所以,事件的本質,是委託欄位的一個包裝器。
a. 這個包裝器對委託欄位訪問器限制作用。
b. 封裝的一個重要功能就是隱藏。
c. 事件對外界隱藏了委託例項的大部分功能,僅暴露了增加/移除事件處理器功能。
5.3 根據.Net 規定,最終修改如下:
/// <summary>
/// 使用EventHandler 用意:
/// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
/// 2. 表明這個委託是用來宣告事件,約束事件處理器
/// 3. 委託示例,儲存事件處理器
/// </summary>
/// <param name="customer"></param>
/// <param name="args"></param>
//public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);
public delegate void OrderEventHandler(Object sender, EventArgs e);
/// <summary>
/// 事件擁有者
/// </summary>
public class Customer
{
/*
//建立儲存事件處理的委託
private OrderEventHandler orderEventHandler;
//建立委託約束的事件 -- 事件最基本的建立放方式,
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}*/
//使用指定的委託型別,定義事件名字
public event OrderEventHandler Order;
public double Bill { get; set; }
public void PlayTheBill()
{
Console.WriteLine("I will pay ¥{0}", this.Bill);
}
public void Walkln()
{
Console.WriteLine("Walk into the restaurant");
}
public void SitDown()
{
Console.WriteLine("Sit down");
}
//事件觸發,都是由事件擁有者,內部一些邏輯方法觸發, 然後呼叫事件處理器
public void Think()
{
for (int ik = 0; ik < 5; ik++)
{
Console.WriteLine("Let me think 。。。。。");
Thread.Sleep(1000);
}
//觸發事件處理器
//if (this.orderEventHandler != null)
if(this.Order != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "KongPao Chicken";
e.Size = "large";
//this.orderEventHandler.Invoke(this, e);
//函式指標用法
//this.orderEventHandler(this, e);
//直接呼叫
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.WriteLine("Please input enter: ");
Console.ReadLine();
this.Walkln();
this.SitDown();
this.Think();
}
}
/// <summary>
/// 事件響應者
/// </summary>
public class Waiter
{
/// <summary>
/// 事件處理器
/// </summary>
/// <param name="customer"></param>
/// <param name="args"></param>
//public void EventAction(Customer customer, OrderEventArgs e)
public void EventAction(Object sender, EventArgs e)
{
Customer customer = sender as Customer;
OrderEventArgs e1 = e as OrderEventArgs;
Console.WriteLine("I will serve you the dish - {0}", e1.DishName);
double price = 10;
switch (e1.Size)
{
case "small":
customer.Bill = price * 0.5;
break;
case "large":
customer.Bill = price * 1.5;
break;
}
}
}
執行事件
static void Main(string[] args)
{
//建立事件擁有者
Customer customer = new Customer();
//建立事件處理者
Waiter waiter = new Waiter();
//註冊事件處理器
customer.Order += waiter.EventAction;
//觸發事件
customer.Action();
customer.PlayTheBill();
}
綜上,委託與事件原來是這層關係。通過劉鐵錳老師講解,有終於理解了,事件是什麼,委託是什麼。
參考《劉鐵錳老師C# 基礎視訊講義》