一、前言 剛開始工作的時候,覺得委托和事件有些神秘,而當你理解他們之後,也覺得好像沒有想象中的那麼難。在項目中運用委托和事件,你會發現他非常棒,這篇博文算是自己對委托和事件的一次梳理和總結。 二、委托 C#中的委托,相當於C++中的指針函數,但委托是面向對象的,是安全的,是一個特殊的類,當然他也是引 ...
一、前言
剛開始工作的時候,覺得委托和事件有些神秘,而當你理解他們之後,也覺得好像沒有想象中的那麼難。在項目中運用委托和事件,你會發現他非常棒,這篇博文算是自己對委托和事件的一次梳理和總結。
二、委托
C#中的委托,相當於C++中的指針函數,但委托是面向對象的,是安全的,是一個特殊的類,當然他也是引用類型,委托傳遞的是對方法的引用。
2.1、delegate
聲明委托就必須使用關鍵字“delegate”,委托是先聲明,後實例化。至少0個參數,至多32個參數
格式如下所示:
private delegate string GetAsString();
委托是一個類,所以他的實例化跟類的實例化一樣,只是他總是接受一個將委托方法作為參數的構造函數。調用委托方法就有兩種方式,如下所示:
int i = 10; var method = new GetAsString(i.ToString); //調用方法一 Console.WriteLine($"method方法{method()}"); //調用方法二 Console.WriteLine($"method.Invoke方法{method.Invoke()}");
運行結果:
2.2、Action
Action是無返回值的泛型委托,可以接受0個至16個傳入參數
Action 表示無參,無返回值的委托
Action<int,string> 表示有傳入參數int,string無返回值的委托
前面我們【Log4Net 日誌記錄的實現】中,就使用了Action。如:
public static void Debug(string message, Action RegistedProperties) { RegistedProperties(); log.Debug(message); }
調用方式為:
PFTLog.Debug("測試擴展欄位", () => { LogicalThreadContext.Properties["LogType"] = "擴展欄位內容"; });
在運行中,直接運行Action中的內容即可。
2.3、Func
Func是有返回值的泛型委托,可以接受0個至16個傳入參數
Func<int> 表示無參,返回值為int的委托
Func<object,string,int> 表示傳入參數為object, string 返回值為int的委托
public static decimal GetTotal(Func<int, int, decimal> func, int a, int b) { return func(a, b); }
調用方式
var total = GetTotal((a, b) => { return (decimal)a + b; }, 1, 2); Console.WriteLine($"結果為{total}");
運行結果
2.4、predicate
predicate 是返回bool型的泛型委托,只能接受一個傳入參數
predicate<int> 表示傳入參數為int 返回bool的委托
定義一個方法:
public static bool FindPoints(int a) { return a >= 60; }
定義Predicate委托
Predicate<int> predicate = FindPoints;
調用
var points = new int[] { 10, 50, 60, 80, 100 }; var result = Array.FindAll(points, predicate); Console.WriteLine($"結果為{string.Join(";", result)}");
運行結果
2.5、多播委托
前面的只包含了一個方法的調用,委托可以包含多個方法,這種委托就叫做多播委托。多播委托利用“+=”和“-+”兩種運算符進行添加和刪除委托。
先定義兩個方法
public static void MultiplyByTwo(double v) { double result = v * 2; Console.WriteLine($"傳值:{v};MultiplyByTwo結果為{result}"); } public static void Square(double v) { double result = v * v; Console.WriteLine($"傳值:{v};Square結果為{result}"); }
然後調用
Action<double> operations = MultiplyByTwo;
operations(1);
operations += Square;
operations(2);
運行結果:
三、事件
事件是基於委托,為委托提供一種發佈/訂閱機制,聲明事件需要使用event關鍵字。
發佈者(Publisher):一個事件的發行者,也稱作是發送者(sender),其實就是個對象,這個對象會自行維護本身的狀態信息,當本身狀態信息變動時,便觸發一個事件,並通知說有的事件訂閱者;
訂閱者(Subscriber):對事件感興趣的對象,也稱為Receiver,可以註冊感興趣的事件,在事件發行者觸發一個事件後,會自動執行這段代碼
是不是看到sender,就有種很熟悉的感覺!!!先不忙著急,我們先看下事件的聲明和使用
有這樣一個應用場景,如果系統有異常,需要及時的通知管理員。那麼需要在我們的日誌記錄裡面添加通知管理員的功能,但是問題來了,該怎麼通知管理員呢?至少現在無法知道。所以我們就需要在使用到事件。
添加代碼如下,如果不知道日誌功能的可以參考【Log4Net 日誌記錄的實現】
//聲明一個通知的委托 public delegate void NoticeEventHander(string message); //在委托的機制下我們建立以個通知事件 public static event NoticeEventHander OnNotice;
調用方式
public static void Debug(string message, Action RegistedProperties) { RegistedProperties(); log.Debug(message); //執行通知 OnNotice?.Invoke($"系統異常,請及時處理,異常信息:{message}"); }
在引用場景的代碼,先定義一個通知管理員的方法(這裡我們直接Console.WriteLine出來)
public static void Notice(string message) { Console.WriteLine($"通知內容為{message}"); }
先註冊,然後觸發異常消息
//註冊方式一 PFTLog.OnNotice += Notice; //註冊方式二 //PFTLog.OnNotice += new PFTLog.NoticeEventHander(Notice); PFTLog.Debug("測試擴展欄位", () => { LogicalThreadContext.Properties["LogType"] = "擴展欄位內容"; });
運行結果
這裡面我只需要定義好發佈者,你可以以任何方式訂閱,是不是很非常簡單。
弄明白了上面的事件,我們在來說說.Net經常出現的object sender和EventArgs e
.Net Framework的編碼規範:
一、委托類型的名稱都應該以EventHandler結束
二、委托的原型定義:有一個void返回值,並接受兩個輸入參數:一個Object 類型,一個 EventArgs類型(或繼承自EventArgs)
三、事件的命名為 委托去掉 EventHandler之後剩餘的部分
四、繼承自EventArgs的類型應該以EventArgs結尾
現在我們以一個新書發佈的自定義事件為例
事件者發佈代碼:
public class BookInfoEventArgs : EventArgs { public BookInfoEventArgs(string bookName) { BookName = bookName; } public string BookName { get; set; } }
public class BookDealer { //泛型委托,定義了兩個參數,一個是object sender,第二個是泛型 TEventArgs 的e //簡化瞭如下的定義 //public delegate void NewBookInfoEventHandler(object sender, BookInfoEventArgs e); //public event NewBookInfoEventHandler NewBookInfo; public event EventHandler<BookInfoEventArgs> NewBookInfo; public void NewBook(string bookName) { RaiseNewBookInfo(bookName); } public void RaiseNewBookInfo(string bookName) { NewBookInfo?.Invoke(this, new BookInfoEventArgs(bookName)); } }
事件訂閱者
public class Consumer { public Consumer(string name) { Name = name; } public string Name { get; set; } public void NewBookHere(object sender, BookInfoEventArgs e) { Console.WriteLine($"用戶:{Name},收到書名為:{ e.BookName}"); } }
事件訂閱和取消訂閱
var dealer = new BookDealer(); var consumer1 = new Consumer("用戶A"); dealer.NewBookInfo += consumer1.NewBookHere; dealer.NewBook("book112"); var consumer2 = new Consumer("用戶B"); dealer.NewBookInfo += consumer2.NewBookHere; dealer.NewBook("book_abc"); dealer.NewBookInfo -= consumer1.NewBookHere; dealer.NewBook("book_all");
運行結果
經過這個例子,我們可以知道Object sender參數代表的是事件發佈者本身,而EventArgs e 也就是監視對象了。深入理解之後,是不是覺得也沒有想象中的那麼難了。
四、總結
這裡我們講了委托和事件,在.Net開發中使用委托和事件,可以減少依賴性和層的耦合,開發出具有更高的重用性的組件。