委託與事件
為何委託和事件難以理解?
C#的委託和事件是比較難以理解的內容,可能工作多年對其定義和使用還是茫然。主要是沒有深刻理解為什麼有委託和事件,使用的主要場景是什麼,帶來了什麼編碼效果,解決了實際中的什麼問題。大多數時候記住的可能是晦澀難懂的書本定義和各種簡短的說明,沒有全面的瞭解而是淺嘗輒止。另外.NET提供的各種語法糖的編碼方式讓原本就不理解的特性蒙上了神祕的色彩。所以記錄一下對兩者的理解,更多的是要在實際的編寫中使用,只有在使用的過程才能加深理解。
一、什麼是委託
委託是定址方法的.NET版本,.NET版本是相對於C或者C++中的函式指標,委託可以看成是.NET的函式指標。區別在於C或者C++的函式指標是指向記憶體的位置,其本質是地址所以無法判斷其實際的指向內容是什麼,體現其是型別不安全,而委託是型別安全,因為委託是一個類和普通類一樣必須要先定義類,通過類建立例項,而定義的類方式是必須指定方法的簽名和返回型別,體現其是型別安全。委託類可以理解成方法的簽名和返回型別的指定名稱。
二、委託使用場景
方法的引數一般是資料型別,現在通過委託將方法進行“包裝”成普通的引數進行傳遞,比如1、在System.Threading.Tasks中啟動執行緒和任務,然後執行相應的方法,則必須為執行緒和任務傳遞執行的方法,通過委託(例項)傳遞引數;2、在物件陣列中進行排序,物件排序依據要自定義,這時可以傳遞委託(例項)定義排序的方式;3、事件,事件是一種釋出訂閱的機制,基於委託實現事件。
三、委託示例
/// <summary> /// 定義委託型別 /// </summary> /// <param name="str"></param>/// <returns></returns> public delegate string SetStrDelegate(string str); class Program { static void Main(string[] args) { // 建立委託例項(建構函式引數是與定義類一致的簽名的方法名稱) SetStrDelegate setStrDelegate = new SetStrDelegate(SetStrMethod);//委託例項作為引數形式傳入 Console.WriteLine(GetStrMethod(setStrDelegate,"委託")); Console.ReadKey(); } /// <summary> /// 建立委託例項的引數(委託作為“地址”指向的方法) /// </summary> /// <param name="str"></param> /// <returns></returns> public static string SetStrMethod(string str) { return string.Format("SetStrMethod({0})", str); } /// <summary> /// 使用委託例項的方法 /// </summary> /// <param name="setStrDelegate"></param> /// <param name="str"></param> /// <returns></returns> public static string GetStrMethod(SetStrDelegate setStrDelegate,string str) { // 通過委託執行方法(或者委託例項呼叫Invoke()方法等同實現) return setStrDelegate(str); }
}
// Action<T>和Func<T>泛型委託 // Action<T>表示引用一個void返回型別的方法,這個委託存在不同的變體,可以傳遞至多16個不同型別引數 // Func<T>表示引用一個帶返回型別的方法,Func<out TResult>,這個委託存在不同變體,可以傳遞至多16個不同型別引數,最後一個引數是返回型別 // 使用這兩個泛型委託簡化了委託型別的定義,可以很方便的使用委託,滿足所有委託型別的要求 class Program { static void Main(string[] args) { // 建立Action<T>委託例項,引數是一個string,返回型別是void Action<string> action = new Action<string>(SetStrActionMethod); // 使用委託例項(可以理解委託方法SetStrActionMethod的執行“委託”給action) action("委託"); SetStrAction(action,"委託"); // 建立Func<T>委託例項,引數是一個string,返回型別是string Func<string, string> func = new Func<string, string>(SetStrFuncMethod); // 使用委託例項 Console.WriteLine(func("委託")); var returnStr = SetStrFunc(func, "委託"); Console.WriteLine(returnStr); Console.ReadKey(); } /// <summary> /// 建立Action<T>委託例項的引數(委託作為“地址”指向的方法) /// </summary> /// <param name="str"></param> public static void SetStrActionMethod(string str) { Console.WriteLine(string.Format("SetStrActionMethod({0})", str)); } /// <summary> /// 使用委託例項的方法 /// </summary> /// <param name="action"></param> /// <param name="str"></param> public static void SetStrAction(Action<string> action, string str) { action(str); } /// <summary> /// 建立Func<T>委託例項的引數(委託作為“地址”指向的方法) /// </summary> /// <param name="str"></param> /// <returns></returns> public static string SetStrFuncMethod(string str) { return string.Format("SetStrFuncMethod({0})", str); } /// <summary> /// 使用委託例項的方法 /// </summary> /// <param name="func"></param> /// <param name="str"></param> /// <returns></returns> public static string SetStrFunc(Func<string,string> func,string str) { return func(str); }
}
/// <summary> /// 匿名方法和lambda表示式與委託,使用委託必須要一個方法存在,而且方法的簽名和委託類的定義一致 /// 通過使用匿名方法和lambda表示式簡化這個過程 /// </summary> class Program { static void Main(string[] args) { // 匿名方法、定義並且建立委託例項 Func<string, string> SetStr = delegate (string str) { return string.Format("匿名方法{0}", str); }; // 使用委託例項 Console.WriteLine( SetStr("委託")); // lambda表達、定義並且建立委託例項 Func<string, string> SetStrLambda = str => { return string.Format("Lambda表示式{0}", str); }; Console.WriteLine(SetStrLambda("委託")); Console.ReadKey(); } }
四、什麼是事件
事件基於委託,為委託提供了釋出訂閱的機制。事件一直是很困惑一塊內容,雖然在winform和webform中使用事件,在JavaScript中也有事件,但僅在使用階段,控制元件的拖拽,建立事件程式碼,然後在事件中編輯業務邏輯內容,不知道事件具體機制和為什麼使用事件。學習ASP.NET MVC的請求管道的19個事件和事件相關的HttpModule時候,通過自定義Module繫結事件(19個事件)可以提供實際業務場景的靈活處理方式。所以想到事件在這個管道和HttpModule的使用中帶來的擴充套件;在專案中使用事件匯流排(EventBus)對業務在程式碼實現的解耦作用,所以在使用中體會到釋出訂閱機制的魅力。定義一個事件,關注和受事件影響者來訂閱事件,而釋出者在一定條件釋出事件,訂閱者監聽事件執行業務。讓釋出者不關心訂閱者如何處理業務,訂閱者也不關心釋出者內部如何釋出事件,定義好事件的含義,通過事件串聯兩者。
事件的神祕性在於C#對其進行了很好的封裝,使用者沒有體會到其具體是怎麼實現的,只要簡單定義事件,訂閱事件,釋出事件就能輕鬆完成這個機制。在Windows中有訊息處理機制,比如滑鼠的點選、鍵盤的按鍵輸入、窗體的變化等都是通過訊息處理機制定義好的,這些都是在底層已經開發封裝好的功能,而C#的事件就是基於windows訊息處理機制,其是更好封裝成開發者使用的功能。
事件與委託的關係,事件表示的是一個動作,一個事情、至於如何實現事件和事件發生後如何處理,就要使用委託。比如租房是一個事件,那麼中介就是委託,只要告訴中介租房的訴求,中介依據要求查詢方案租賃房子,完成這個事件;滑鼠發生了點選事件,至於點選事件應該如何處理則由關注或註冊了這個事件的委託處理程式進行處理,完成這個事件。
五、事件的使用場景
事件使用很多,winform和webform的事件驅動,ASP.NET MVC的管道模式的事件,設計模式釋出訂閱機制,事件匯流排。
六、事件示例
// 事件有事件源和事件處理程式組成,類A事件源、類B事件處理 // 定義委託,為類B的事件處理方法定義委託型別 public delegate void handler(object sender, MyEventArgs args); class Program { static void Main(string[] args) { A a = new A(); B b = new B(a); // 建構函式注入 // 定義事件源例項 MyEventArgs arg = new MyEventArgs() { id = 1, name = "事件源引數", title = "事件源類A釋出事件" }; // 事件源類A釋出事件 a.Happen(arg); Console.ReadKey(); } } // 事件源 public class A { // 定義事件,訂閱事件的委託 public event handler Event; // 釋出事件 public void Happen(MyEventArgs args) { if (Event != null) { Event(this,args); } } } // 事件處理,處理事件 public class B { public B(A a) { // 建立委託例項 handler handler = new handler(OnHandler); // 將委託例項繫結事件,當事件發生時候,通過委託呼叫委託例項化的方法(OnHandler),進行事件的訂閱(註冊) a.Event += handler; } // 事件處理方法與委託型別的具有一致的簽名 public void OnHandler(object sender, MyEventArgs args) { Console.WriteLine("id=>{0},name=>{1},title=>{2}", args.id, args.name, args.title); } } // 事件資料(繼承基類,傳輸事件源的引數) public class MyEventArgs : EventArgs { public int id { get; set; } public string name { get; set; } public string title { get; set; } }
總結、委託和事件難以理解是因為委託和事件都是經過很好的封裝,所以使用者不容易接受。委託的使用是使方法作為引數的形式傳入方法,在方法中直接呼叫方法也可以實現一樣的效果,為什麼還要使用委託這個特性,我個人理解1、是在一定的場景下,如果方法的業務是變化則不容易進行擴充套件,使用委託則可以提供靈活性,通過定義不同的業務場景,傳入不同的方法,實現不同的業務。2、委託為其他特性提供了基礎,最明顯就是事件。
ps:對於委託和事件一直存在疑問,這個是什麼?是如何實現?為什麼要使用這個特性?這個特性給程式設計帶來什麼效果?如何在實際場景中使用?在這些問題不斷地反問中記錄下這些內容。因為使用的頻率少,所以慢慢的就會忘記,在不懂的時候可以翻出來看看。
參考文獻