前言 在本章中,主要是藉機這個C#基礎篇的系列整理過去的學習筆記、歸納總結並更加理解透徹。 在上一篇文章,我們已經對委托有了進一步瞭解,委托相當於用方法作為另一方法參數,同時,也可以實現在兩個不能直接調用的方法中做橋梁。 下麵我們來回顧一下委托的例子。 public delegate void Ex ...
前言
在本章中,主要是藉機這個C#基礎篇的系列整理過去的學習筆記、歸納總結並更加理解透徹。
在上一篇文章,我們已經對委托有了進一步瞭解,委托相當於用方法作為另一方法參數,同時,也可以實現在兩個不能直接調用的方法中做橋梁。
下麵我們來回顧一下委托的例子。
public delegate void ExecutingDelegate(string name);
public class ExecutingManager
{
public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
{
ToExecuting(name);
}
}
private static void StartExecute(string name)
{
Console.WriteLine("開始執行:" + name);
}
private static void EndExecute(string name)
{
Console.WriteLine("結束執行:" + name);
}
static void Main(string[] args)
{
ExecutingManager exec = new ExecutingManager();
exec.ExecuteProgram("開始。。。", StartExecute);
exec.ExecuteProgram("結束。。。", EndExecute);
Console.ReadKey();
}
根據上述的示例,再利用上節學到的知識,將多個方法綁定到同一個委托變數實現多播,該如何做呢?
再次修改代碼:
static void Main(string[] args)
{
ExecutingManager exec = new ExecutingManager();
ExecutingDelegate executingDelegate;
executingDelegate = StartExecute;
executingDelegate += EndExecute;
exec.ExecuteProgram("yuan", executingDelegate);
Console.ReadKey();
}
但是,此刻我們發現是不是可以將實例化聲明委托的變數封裝到ExecutingManager類中,這樣是不是更加方便調用呢?
public class ExecutingManager
{
/// <summary>
/// 在 ExecutingManager 類的內部聲明 executingDelegate 變數
/// </summary>
public ExecutingDelegate executingDelegate;
public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
{
ToExecuting(name);
}
}
static void Main(string[] args)
{
ExecutingManager exec = new ExecutingManager();
exec.executingDelegate = StartExecute;
exec.executingDelegate += EndExecute;
exec.ExecuteProgram("yuan", exec.executingDelegate);
Console.ReadKey();
}
寫到這裡了,這樣做沒有任何問題,但我們發現這條語句很奇怪。在調用exec.ExecuteProgram方法的時候,再次傳遞了exec的executingDelegate欄位, 既然如此,我們何不修改 ExecutingManager類成這樣:
public class ExecutingManager
{
/// <summary>
/// 在 GreetingManager 類的內部聲明 delegate1 變數
/// </summary>
public ExecutingDelegate executingDelegate;
public void ExecuteProgram(string name)
{
if (executingDelegate != null) // 如果有方法註冊委托變數
{
executingDelegate(name); // 通過委托調用方法
}
}
}
static void Main(string[] args)
{
ExecutingManager exec = new ExecutingManager();
exec.executingDelegate = StartExecute;
exec.executingDelegate += EndExecute;
exec.ExecuteProgram("yuan");
Console.ReadKey();
}
這樣再看,發現調用一下就更加簡潔了。
正文
在日常生活中,我們可能都會遇到這樣的各種各樣的事情,而對於這些事情我們都會採取相應的措施。比如,當你要給一個女神過生日的時候,你就可以給她送禮物。而這種情況,在C#開發中,就相當於過生日被當作事件來對待,而送禮物就是事件做出的響應。
當女神過生日的時候,女神就會發佈生日事件,而你就會接受到這個事件的通知,並做出響應的處理(送禮物等騷操作)。其中,觸發這個事件的對象我們可稱之為事件發佈者,而捕獲這個事件並做出相應處理的稱之為事件訂閱者,我們可以看出,女神就是充當了發佈者,而你自己則充當了訂閱者。
這裡由生日事件引申出兩類角色,即事件發佈者和事件訂閱者。
開始
1.發佈者/訂閱者模式
在開發中,我們是否遇到這樣的情景,當一個特定的程式事件發生時,其他程式部分可以得到該事件註冊發生通知。
發佈者定義一系列事件,並提供一個註冊方法;訂閱者向發佈者註冊,並提供一個可被回調的方法,也就是事件處理程式;當事件被觸發的時候,訂閱者得到通知,而訂閱者所提交的所有方法會被執行。
- 發佈者:發佈某個事件的類或結構,其他類可以在該事件發生時得到通知。
- 訂閱者:註冊併在事件發生時得到通知的類或結構。
- 事件處理程式:由訂閱者註冊到事件的方法,在發佈者觸發事件時執行。事件處理程式方法可以定義在事件所在的類或結果中,也可以定義在不同的類或結構中。
- 觸發事件:調用事件的術語。當事件觸發時,所有註冊到它的方法都會被一次調用。
2.基本使用
/// <summary>
/// 先自定義一個委托
/// </summary>
/// <param name="oldPrice"></param>
/// <param name="newPrice"></param>
public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
/// <summary>
/// 這個一個發佈者
/// </summary>
public class IPhone
{
decimal price;
/// <summary>
/// 定義一個事件
/// event 用來定義事件
/// PriceChangedHandler委托類型,事件需要通過委托來調用訂閱者需要的方法
/// </summary>
public event PriceChangedHandler PriceChanged;
public decimal Price
{
get { return price; }
set
{
if (price == value)
return;
decimal oldPrice = price;
price = value; // 如果調用列表不為空,則觸發。
if (PriceChanged != null) //用來判斷事件是否被訂閱者註冊過
PriceChanged(oldPrice, price); //調用事件
}
}
}
/// <summary>
/// 這個一個訂閱者
/// </summary>
/// <param name="oldPrice"></param>
/// <param name="price"></param>
static void iPhone_PriceChanged(decimal oldPrice, decimal price)
{
Console.WriteLine("618促銷活動,全場手機 只賣 " + price + " 元, 原價 " + oldPrice + " 元,快來搶!");
}
static void Main()
{
///實例化一個發佈者類
IPhone phone = new IPhone()
{
Price = 5288
}; // 訂閱事件
phone.PriceChanged += iPhone_PriceChanged; //完成事件的註冊 調整價格(事件發生)
phone.Price = 3999; //激發事件,並調用事件
Console.ReadKey();
}
輸出:
618促銷活動,全場手機 只賣 3999 元, 原價 5288 元,快來搶!
3.解析
- 委托類型聲明:事件與事件處理程式必須有共同的簽名和返回類型,它們通過委托類型進行描述。
- 事件聲明:使用關鍵字evet來聲明一個事件,當聲明的事件為一個public時,稱為發佈了一個事件。
- 事件註冊:訂閱者通過+=操作符來註冊事件,並提供一個事件處理程式。
- 事件處理程式: 訂閱者向事件註冊的方法,它可以是顯示命名的方法、匿名方法或者Lambda表達式
- 觸發事件:發佈者用來調用事件的代碼
4.語法
事件的聲明語法:
//聲明一個事件
public [static] event EventHandler EventName;
//聲明多個同類型的事件
public [static] event EventHandler EventName1, EventName2, EventName3;
事件必須聲明在類或結構中,因為事件它不是一個類型,它是一個類或者結構中的一員。
在事件被觸發之前,可以通過和null做比較,判斷是否包含事件註冊處理程式。因為事件成員被初始化預設是null。
委托類型EventHandler是聲明專門用來事件的委托。事件提供了對委托的結構化訪問;也即是無法直接訪問事件中的委托。
5.用法
查看源碼:
事件的標準模式就是System命名空間下聲明的EventHandler委托類型。
EventArgs是System下的一個類,如下:
using System.Runtime.InteropServices;
namespace System
{
[Serializable]
[ComVisible(true)]
[__DynamicallyInvokable]
public class EventArgs
{
[__DynamicallyInvokable]
public static readonly EventArgs Empty = new EventArgs();
[__DynamicallyInvokable]
public EventArgs()
{
}
}
}
根據EventArgs源碼看出,EventArgs本身無法保存和傳遞數據的。
如果想保存和傳遞數據,可以實現一個EventArgs的派生類,然後定義相關的欄位來保存和傳遞參數。
public class IPhone
{
decimal price;
/// <summary>
/// 使用EventHandler定義一個事件
/// </summary>
public event EventHandler PriceChanged;
protected virtual void OnPriceChanged()
{
if (PriceChanged != null)
PriceChanged(this, null);
}
public decimal Price
{
get { return price; }
set
{
if (price == value) return;
decimal oldPrice = price;
price = value; // 如果調用列表不為空,則觸發。
if (PriceChanged != null) // //用來判斷事件是否被訂閱者註冊過
OnPriceChanged();
}
}
}
/// <summary>
/// 這個一個訂閱者
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void iphone_PriceChanged(object sender, EventArgs e)
{
Console.WriteLine("年終大促銷,快來搶!");
}
static void Main()
{
IPhone phone = new IPhone()
{
Price = 5288M
}; // 訂閱事件
phone.PriceChanged += iphone_PriceChanged;
// 調整價格(事件發生)
phone.Price = 3999;
Console.ReadKey();
}
通過擴展EventHanlder來傳遞數據
System下另有泛型EventHandler類。由此,這裡我們可以將派生於EventArgs的類作為類型參數傳遞過來,這樣,既可以獲得派生類保存的數據。
///擴展類
public class PriceChangedEventArgs : System.EventArgs
{
public readonly decimal OldPrice;
public readonly decimal NewPrice;
public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
{
OldPrice = oldPrice;
NewPrice = newPrice;
}
}
public class IPhone
{
decimal price;
public event EventHandler<PriceChangedEventArgs> PriceChanged;
protected virtual void OnPriceChanged(PriceChangedEventArgs e)
{
if (PriceChanged != null)
PriceChanged(this, e);
}
public decimal Price
{
get { return price; }
set
{
if (price == value) return;
decimal oldPrice = price;
price = value; // 如果調用列表不為空,則觸發。
if (PriceChanged != null)
OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
}
}
}
static void iphone_PriceChanged(object sender, PriceChangedEventArgs e)
{
Console.WriteLine("618促銷活動,全場手機 只賣 " + e.NewPrice + " 元, 原價 " + e.OldPrice + " 元,快來搶!");
}
static void Main()
{
IPhone phone = new IPhone()
{
Price = 5288M
}; // 訂閱事件
phone.PriceChanged += iphone_PriceChanged;
// 調整價格(事件發生)
phone.Price = 3999;
Console.ReadKey();
}
輸出
618促銷活動,全場手機 只賣 3999 元, 原價 5288 元,快來搶!
6.移除事件
可以利用 -= 運算符處理程式從事件中移除,當程式處理完後,可以將事件從中把它移除掉。
class Publiser
{
public event EventHandler SimpleEvent;
public void RaiseTheEvent()
{
SimpleEvent(this, null);
}
}
class Subscriber
{
public void MethodA(object o, EventArgs e) { Console.WriteLine("A"); }
public void MethodB(object o, EventArgs e) { Console.WriteLine("B"); }
}
static void Main(string[] args)
{
Publiser p = new Publiser();
Subscriber s = new Subscriber();
p.SimpleEvent += s.MethodA;
p.SimpleEvent += s.MethodB;
p.RaiseTheEvent();
Console.WriteLine("\n移除B事件處理程式");
p.SimpleEvent -= s.MethodB;
p.RaiseTheEvent();
Console.ReadKey();
}
輸出:
7.事件訪問器
運算符+= 、-=事件允許的唯一運算符。這些運算符是有預定義的行為。然而,我們可以修改這些運算符的行為,讓事件執行任何我們希望定義的代碼。
可以通過為事件定義事件訪問器,來控制事件運算符+=、-=運算符的行為
- 兩個訪問器: add 和 remove
- 聲明事件的訪問器看上去和聲明一個熟悉差不多。
下麵示例演示了具有訪問器的聲明.兩個訪問器都有叫做value的隱式值參數,它接受實例或靜態方法的引用
public event EventHandler Elapsed
{
add
{
//... 執行+=運算符的代碼
}
remove
{
//... 執行-=運算符的代碼
}
}
聲明瞭事件訪問器後,事件不包含任何內嵌委托對象.我們必須實現自己的機制來存儲和移除事件的方法。
事件訪問器表現為void方法,也就是不能使用會返回值的return語句。
示例:
//聲明一個delegate
delegate void EventHandler();
class MyClass
{
//聲明一個成員變數來保存事件句柄(事件被激發時被調用的delegate)
private EventHandler m_Handler = null;
//激發事件
public void FireAEvent()
{
if (m_Handler != null)
{
m_Handler();
}
}
//聲明事件
public event EventHandler AEvent
{
//添加訪問器
add
{
//註意,訪問器中實際包含了一個名為value的隱含參數
//該參數的值即為客戶程式調用+=時傳遞過來的delegate
Console.WriteLine("AEvent add被調用,value的HashCode為:" + value.GetHashCode());
if (value != null)
{
//設置m_Handler域保存新的handler
m_Handler = value;
}
}
//刪除訪問器
remove
{
Console.WriteLine("AEvent remove被調用,value的HashCode為:" + value.GetHashCode());
if (value == m_Handler)
{
//設置m_Handler為null,該事件將不再被激發
m_Handler = null;
}
}
}
}
static void Main(string[] args)
{
MyClass obj = new MyClass();
//創建委托
EventHandler MyHandler = new EventHandler(MyEventHandler);
MyHandler += MyEventHandle2;
//將委托註冊到事件
obj.AEvent += MyHandler;
//激發事件
obj.FireAEvent();
//將委托從事件中撤銷
obj.AEvent -= MyHandler;
//再次激發事件
obj.FireAEvent();
Console.ReadKey();
}
//事件處理程式
static void MyEventHandler()
{
Console.WriteLine("This is a Event!");
}
//事件處理程式
static void MyEventHandle2()
{
Console.WriteLine("This is a Event2!");
}
輸出:
總結
- 這節對事件的基本使用,以及事件的標準語法、事件訪問器等多個地方進行說明,大致可以瞭解和掌握事件的基本使用。
- 結合上一篇的委托和這一節的事件,委托和事件我們大概掌握了基本用法。並加以實踐,結合實際開發,應用其中。
- 如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步。
參考 文檔 《C#圖解教程》
註:搜索關註公眾號【DotNet技術谷】--回覆【C#圖解】,可獲取 C#圖解教程文件