在本章中,主要是藉機這個C#基礎篇的系列整理過去的學習筆記、概括總結並更加理解透徹。html
在上一篇文章,咱們已經對委託有了進一步瞭解,委託至關於用方法做爲另外一方法參數,同時,也能夠實如今兩個不能直接調用的方法中作橋樑。c#
下面咱們來回顧一下委託的例子。iphone
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(); }
根據上述的示例,再利用上節學到的知識,將多個方法綁定到同一個委託變量實現多播,該如何作呢?ide
再次修改代碼:學習
static void Main(string[] args) { ExecutingManager exec = new ExecutingManager(); ExecutingDelegate executingDelegate; executingDelegate = StartExecute; executingDelegate += EndExecute; exec.ExecuteProgram("yuan", executingDelegate); Console.ReadKey(); }
可是,此刻咱們發現是否是能夠將實例化聲明委託的變量封裝到ExecutingManager類中,這樣是否是更加方便調用呢?ui
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類成這樣:this
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(); }
這樣再看,發現調用一下就更加簡潔了。spa
在平常生活中,咱們可能都會遇到這樣的各類各樣的事情,而對於這些事情咱們都會採起相應的措施。好比,當你要給一個女神過生日的時候,你就能夠給她送禮物。而這種狀況,在C#開發中,就至關於過生日被看成事件來對待,而送禮物就是事件作出的響應。code
當女神過生日的時候,女神就會發布生日事件,而你就會接受到這個事件的通知,並作出響應的處理(送禮物等騷操做)。其中,觸發這個事件的對象咱們可稱之爲事件發佈者,而捕獲這個事件並作出相應處理的稱之爲事件訂閱者,咱們能夠看出,女神就是充當了發佈者,而你本身則充當了訂閱者。htm
這裏由生日事件引伸出兩類角色,即事件發佈者和事件訂閱者。
在開發中,咱們是否遇到這樣的情景,當一個特定的程序事件發生時,其餘程序部分能夠獲得該事件註冊發生通知。
發佈者定義一系列事件,並提供一個註冊方法;訂閱者向發佈者註冊,並提供一個可被回調的方法,也就是事件處理程序;當事件被觸發的時候,訂閱者獲得通知,而訂閱者所提交的全部方法會被執行。
/// <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 元,快來搶!
事件的聲明語法:
//聲明一個事件 public [static] event EventHandler EventName; //聲明多個同類型的事件 public [static] event EventHandler EventName1, EventName2, EventName3;
事件必須聲明在類或結構中,由於事件它不是一個類型,它是一個類或者結構中的一員。
在事件被觸發以前,能夠經過和null作比較,判斷是否包含事件註冊處理程序。由於事件成員被初始化默認是null。
委託類型EventHandler是聲明專門用來事件的委託。事件提供了對委託的結構化訪問;也便是沒法直接訪問事件中的委託。
查看源碼:
事件的標準模式就是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 元,快來搶!
能夠利用 -= 運算符處理程序從事件中移除,當程序處理完後,能夠將事件從中把它移除掉。
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(); }
輸出:
運算符+= 、-=事件容許的惟一運算符。這些運算符是有預約義的行爲。然而,咱們能夠修改這些運算符的行爲,讓事件執行任何咱們但願定義的代碼。
能夠經過爲事件定義事件訪問器,來控制事件運算符+=、-=運算符的行爲
下面示例演示了具備訪問器的聲明.兩個訪問器都有叫作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#圖解教程文件