程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [轉載]C#深化剖析委托與事情

[轉載]C#深化剖析委托與事情

編輯:C#入門知識

[轉載]C#深化剖析委托與事情。本站提示廣大學習愛好者:([轉載]C#深化剖析委托與事情)文章只能為提供參考,不一定能成為您想要的結果。以下是[轉載]C#深化剖析委托與事情正文


原文出處:

作者:風塵浪子

原文鏈接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html

同類鏈接:http://www.cnblogs.com/SkySoot/archive/2012/04/05/2433639.html

引言

本篇文章將為你引見一下 Delegate 的運用方式,逐步揭開 C# 當中事情(Event)的由來,它能使處置委托類型的進程變得愈加復雜。
還將為您解釋委托的協變與逆變,以及如何運用 Delegate 使 Observer(察看者)形式的運用變得愈加復雜。
在事情的引見上,會講述事情的運用方式,並以ASP.NET的用戶控件為例子,引見一下自定義事情的運用。
最後一節,將引見Predicate<T>、Action<T>、Func<T,TResult>多種泛型委托的運用和Lambda的開展進程與其運用方式。
由於時間匆促,文中有錯誤的中央敬請點評。

 

目錄

一、委托類型的因由

二、樹立委托類

三、委托運用方式

四、深化解析事情

五、Lambda 表達式

 

一、委托類型的因由

記得在運用C言語的年代,整個項目中都充溢著針指的身影,那時分盛行運用函數指針來創立回調函數,運用回調可以把函數回調給順序中的另一個函數。但函數指針只是復雜地把地址指向另一個函數,並不能傳遞其他額定信息。
在.NET中,在大局部時間裡都沒有指針的身影,由於指針被封鎖在外部函數當中。可是回調函數卻仍然存在,它是以委托的方式來完成的。委托可以被視為一個更初級的指針,它不只僅能把地址指向另一個函數,而且還能傳遞參數,前往值等多個信息。零碎還為委托對象自動生成了同步、異步的調用方式,開發人員運用 BeginInvoke、EndInvoke 辦法就可以拋開 Thread 而直接運用多線程調用 。

回到目錄

 

二、樹立委托類

運用delegate就可以直接樹立任何稱號的委托類型,當停止零碎編譯時,零碎就會自動生成此類型。您可以運用delegate void MyDelegate() 方式樹立一個委托類,並運用ILDASM.exe察看其成員。由ILDASM.exe 中可以看到,它承繼了System.MulticastDelegate類,並自動生成BeginInvoke、EndInvoke、Invoke 等三個常用辦法。

Invoke 辦法是用於同步伐用委托對象的對應辦法,而BeginInvoke、EndInvoke是用於以異步方式調用對應辦法的。
關於異步伐用的運用方式,可以參考:C#綜合揭秘——細說多線程

復制代碼
1      public class MyDelegate:MulticastDelegate
2 {
3 //同步伐用委托辦法
4 public virtual void Invoke();
5 //異步伐用委托辦法
6 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state);
7 public virtual void EndInvoke(IAsyncResult result);
8 }
復制代碼

MulticastDelegate是System.Delegate的子類,它是一個特殊類,編譯器和其他工具可以從此類派生,但是自定義類不能顯式地從此類停止派生。它支持多路播送委托,並擁有一個帶有鏈接的委托列表,在調用多路播送委托時,零碎將依照調用列表中的委托呈現順序來同步伐用這些委托。

MulticastDelegate具有兩個常用屬性:Method、Target。其中Method 用於獲取委托所表示的辦法Target 用於獲取以後調用的類實例。

MulticastDelegate有以下幾個常用辦法:

辦法稱號闡明  Clone   創立委托的淺表正本。  GetInvocationList   依照調用順序前往此多路播送委托的調用列表。  GetMethodImpl   前往由以後的 MulticastDelegate 表示的靜態辦法。  GetObjectData   用序列化該實例所需的一切數據填充 SerializationInfo 對象。  MemberwiseClone   創立以後 Object 的淺表正本。  RemoveImpl   調用列表中移除與指定委托相等的元素

MulticastDelegate與Delegate給委托對象樹立了弱小的支持,上面向各位詳細引見一下委托的運用方式。

回到目錄

 

三、委托運用方式

3.1 復雜的委托

當樹立委托對象時,委托的參數類型必需與委托辦法絕對應。只需向樹立委托對象的結構函數中輸出辦法稱號example.Method,委托就會直接綁定此辦法。運用myDelegate.Invoke(string message),就能顯式調用委托辦法。但在實踐的操作中,我們無須用到 Invoke 辦法,而只需直接運用myDelegate(string message),就能調用委托辦法。

復制代碼
 1     class Program
2 {
3 delegate void MyDelegate(string message);
4
5 public class Example
6 {
7 public void Method(string message)
8 {
9 MessageBox.Show(message);
10 }
11 }
12
13 static void Main(string[] args)
14 {
15 Example example=new Example();
16 MyDelegate myDelegate=new MyDelegate(example.Method);
17 myDelegate("Hello World");
18 Console.ReadKey();
19 }
20 }
復制代碼

 

3.2 帶前往值的委托

當樹立委托對象時,委托的前往值必需與委托辦法絕對應。運用上面的例子,辦法將前往 “Hello Leslie” 。

復制代碼
 1     class Program
2 {
3 delegate string MyDelegate(string message);
4
5 public class Example
6 {
7 public string Method(string name)
8 {
9 return "Hello " + name;
10 }
11 }
12
13 static void Main(string[] args)
14 {
15 Example example=new Example();
16 //綁定委托辦法
17 MyDelegate myDelegate=new MyDelegate(example.Method);
18 //調用委托,獲取前往值
19 string message = myDelegate("Leslie");
20 Console.WriteLine(message);
21 Console.ReadKey();
22 }
23 }
復制代碼

 

3.3 多路播送委托

在第二節前已經提過,委托類承繼於MulticastDelegate,這使委托對象支持多路播送,即委托對象可以綁定多個辦法。當輸出參數後,每個辦法會按順序停止迭代處置,並前往最後一個辦法的計算後果。
上面的例子中,Price 類中有兩個計算辦法,Ordinary 按普通的9.5折計算,Favourable 按優惠價 8.5 折計算。委托同時綁定了這兩個辦法,在輸出參數100當前,Ordinary、Favourable這兩個辦法將按順序迭代執行下去,最後前往 Favourable 辦法的計算後果 85。

復制代碼
 1         delegate double MyDelegate(double message);
2
3 public class Price
4 {
5 public double Ordinary(double price)
6 {
7 double price1 = 0.95 * price;
8 Console.WriteLine("Ordinary Price : "+price1);
9 return price1;
10 }
11
12 public double Favourable(double price)
13 {
14 double price1 = 0.85 * price;
15 Console.WriteLine("Favourable Price : " + price1);
16 return price1;
17 }
18
19 static void Main(string[] args)
20 {
21 Price price = new Price();
22 //綁定Ordinary辦法
23 MyDelegate myDelegate = new MyDelegate(price.Ordinary);
24 //綁定Favourable辦法
25 myDelegate += new MyDelegate(price.Favourable);
26 //調用委托
27 Console.WriteLine("Current Price : " + myDelegate(100));
28 Console.ReadKey();
29 }
30 }
復制代碼

運轉後果


3.4 淺談Observer形式

回憶一下復雜的 Observer 形式,它運用一對多的方式,可以讓多個察看者同時關注同一個事物,並作出不同的呼應。
例如上面的例子,Manager的底薪為根本工資的1.5倍,Assistant的底薪為根本工資的1.2倍。WageManager類的RegisterWorker辦法與RemoveWorker辦法可以用於注冊和登記察看者,最後執行Execute辦法可以對多個已注冊的察看者同時輸出參數。

 

 

復制代碼
 1     public class WageManager
2 {
3 IList<Worker> workerList = new List<Worker>();
4
5 public void RegisterWorker(Worker worker)
6 {
7 workerList.Add(worker);
8 }
9
10 public void RemoveWorker(Worker worker)
11 {
12 workerList.Remove(worker);
13 }
14
15 public void Excute(double basicWages)
16 {
17 if (workerList.Count != 0)
18 foreach (var worker in workerList)
19 worker.GetWages(basicWages);
20 }
21
22 static void Main(string[] args)
23 {
24 WageManager wageManager = new WageManager();
25 //注冊察看者
26 wageManager.RegisterWorker(new Manager());
27 wageManager.RegisterWorker(new Assistant());
28 //同時輸出底薪3000元,辨別停止計算
29 wageManager.Excute(3000);
30
31 Console.ReadKey();
32 }
33 }
34
35 public abstract class Worker
36 {
37 public abstract double GetWages(double basicWages);
38 }
39
40 public class Manager:Worker
41 {
42 //Manager實踐工資為底薪1.5倍
43 public override double GetWages(double basicWages)
44 {
45 double totalWages = 1.5 * basicWages;
46 Console.WriteLine("Manager's wages is " + totalWages);
47 return totalWages;
48 }
49 }
50
51 public class Assistant : Worker
52 {
53 //Assistant實踐工資為底薪的1.2倍
54 public override double GetWages(double basicWages)
55 {
56 double totalWages = 1.2 * basicWages;
57 Console.WriteLine("Assistant's wages is " + totalWages);
58 return totalWages;
59 }
60 }
復制代碼

運轉後果

 

開發 Observer 形式時借助委托,可以進一步簡化開發的進程。由於委托對象支持多路播送,所以可以把Worker類省略。在WageManager類中樹立了一個委托對象wageHandler,經過Attach與Detach辦法可以辨別參加或取消委托。假如察看者想對事物停止監測,只需求參加一個委托對象即可。記得在第二節已經提過,委托的GetInvodationList辦法能獲取多路播送委托列表,在Execute辦法中,就是經過去多路播送委托列表去判別所綁定的委托數量能否為0。

復制代碼
 1         public delegate double Handler(double basicWages);
2
3 public class Manager
4 {
5 public double GetWages(double basicWages)
6 {
7 double totalWages=1.5 * basicWages;
8 Console.WriteLine("Manager's wages is : " + totalWages);
9 return totalWages;
10 }
11 }
12
13 public class Assistant
14 {
15 public double GetWages(double basicWages)
16 {
17 double totalWages = 1.2 * basicWages;
18 Console.WriteLine("Assistant's wages is : " + totalWages);
19 return totalWages;
20 }
21 }
22
23 public class WageManager
24 {
25 private Handler wageHandler;
26
27 //參加察看者
28 public void Attach(Handler wageHandler1)
29 {
30 wageHandler += wageHandler1;
31 }
32
33 //刪除察看者
34 public void Detach(Handler wageHandler1)
35 {
36 wageHandler -= wageHandler1;
37 }
38
39 //經過GetInvodationList辦法獲取多路播送委托列表,假如察看者數量大於0即執行辦法
40 public void Execute(double basicWages)
41 {
42 if (wageHandler!=null)
43 if(wageHandler.GetInvocationList().Count() != 0)
44 wageHandler(basicWages);
45 }
46
47 static void Main(string[] args)
48 {
49 WageManager wageManager = new WageManager();
50 //參加Manager察看者
51 Manager manager = new Manager();
52 Handler managerHandler = new Handler(manager.GetWages);
53 wageManager.Attach(managerHandler);
54
55 //參加Assistant察看者
56 Assistant assistant = new Assistant();
57 Handler assistantHandler = new Handler(assistant.GetWages);
58 wageManager.Attach(assistantHandler);
59
60 //同時參加底薪3000元,辨別停止計算
61 wageManager.Execute(3000);
62 Console.ReadKey();
63 }
64 }
復制代碼

最後運轉後果與下面的例子相反。

 

3.5 委托的協變與逆變

在 Framework 2.0 呈現之前,委托協變這個概念還沒有呈現。此時由於委托是平安類型,它們不恪守承繼的根底規則。即會這上面的狀況:Manager 雖然是 Worker 的子類,但 GetWorkerHander 委托不能直接綁定 GetManager 辦法,由於在委托當中它們的前往值 Manager 與 Worker 被視為完全有關的兩個類型。

復制代碼
 1      public class Worker
2 {.......}
3 public class Manager:Worker
4 {.......}
5
6 class Program
7 {
8 public delegate Worker GetWorkerHandler(int id);
9 public delegate Manager GetManagerHandler(int id);
10
11 public static Worker GetWorker(int id)
12 {
13 Worker worker = new Worker();
14 ..............
15 return worker;
16 }
17
18 public static Manager GetManager(int id)
19 {
20 Manager manager = new Manager();
21 ..............
22 return manager;
23 }
24
25 static void Main(string[] args)
26 {
27 GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);
28 var worker=workerHandler(1);
29
30 GetManagerHandler managerHandler = new GetManagerHandler(GetManager);
31 var manager = managerHandler(2);
32 Console.ReadKey();
33 }
34 }
復制代碼

自從Framework 2.0 面試當前,委托協變的概念就應運而生,此時委托可以依照傳統的承繼規則停止轉換。即 GetWorkerHandler 委托可以直接綁定 GetManager 辦法。

復制代碼
 1      public class Worker
2 {.......}
3 public class Manager:Worker
4 {.......}
5
6 class Program
7 {
8 public delegate Worker GetWorkerHandler(int id);
9 //在 Framework2.0 以上,委托 GetWorkerHandler 可綁定 GetWorker 與 GetManager 兩個辦法
10
11 public static Worker GetWorker(int id)
12 {
13 Worker worker = new Worker();
14 return worker;
15 }
16
17 public static Manager GetManager(int id)
18 {
19 Manager manager = new Manager();
20 return manager;
21 }
22
23 static void Main(string[] args)
24 {
25 GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);
26 Worker worker=workerHandler(1);
27 GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager);
28 Manager manager = managerHandler(2) as Manager;
29 Console.ReadKey();
30 }
31 }
復制代碼

委托逆變,是指委托辦法的參數異樣可以接納 “承繼” 這個傳統規則。像上面的例子,以 object 為參數的委托,可以承受任何 object 子類的對象作為參數。最後可以在處置辦法中運用 is 對輸出數據的類型停止判別,辨別處置對不同的類型的對象。

復制代碼
 1     class Program
2 {
3 public delegate void Handler(object obj);
4
5 public static void GetMessage(object message)
6 {
7 if (message is string)
8 Console.WriteLine("His name is : " + message.ToString());
9 if (message is int)
10 Console.WriteLine("His age is : " + message.ToString());
11 }
12
13 static void Main(string[] args)
14 {
15 Handler handler = new Handler(GetMessage);
16 handler(29);
17 Console.ReadKey();
18 }
19 }
復制代碼

運轉後果

留意委托與其綁定辦法的參數必需一至,即當 Handler 所輸出的參數為 A 類型,其綁定辦法 GetMessage 的參數也必需為 A 類或許 A 的父類 。相反,當綁定辦法的參數為 A 的子類,零碎也無法識別。

 
3.6 泛型委托

委托逆變雖然適用,但假如都以 object 作為參數,則需求每次都對參數停止類型的判別,這不由令人感到膩煩。
為此,泛型委托應運而生,泛型委托有著委托逆變的優點,同時應用泛型的特性,可以使一個委托綁定多個不同類型參數的辦法,而且在辦法中不需求運用 is 停止類型判別,從而簡化了代碼。

復制代碼
 1     class Program
2 {
3 public delegate void Handler<T>(T obj);
4
5 public static void GetWorkerWages(Worker worker)
6 {
7 Console.WriteLine("Worker's total wages is " + worker.Wages);
8 }
9
10 public static void GetManagerWages(Manager manager)
11 {
12 Console.WriteLine("Manager's total wages is "+manager.Wages);
13 }
14
15 static void Main(string[] args)
16 {
17 Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages);
18 Worker worker = new Worker();
19 worker.Wages = 3000;
20 workerHander(worker);
21
22 Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages);
23 Manager manager = new Manager();
24 manager.Wages = 4500;
25 managerHandler(manager);
26
27 Console.ReadKey();
28 }
29 }
復制代碼

運轉後果

回到目錄

四、深化解析事情

4.1 事情的由來

在引見事情之前大家可以先看看上面的例子, PriceManager 擔任對商品價錢停止處置,當委托對象 GetPriceHandler 的前往值大於100元,按8.8折計算,低於100元按原價計算。

復制代碼
 1     public delegate double PriceHandler();
2
3 public class PriceManager
4 {
5 public PriceHandler GetPriceHandler;
6
7 //委托處置,當價錢高於100元按8.8折計算,其他按原價計算
8 public double GetPrice()
9 {
10 if (GetPriceHandler.GetInvocationList().Count() > 0)
11 {
12 if (GetPriceHandler() > 100)
13 return GetPriceHandler()*0.88;
14 else
15 return GetPriceHandler();
16 }
17 return -1;
18 }
19 }
20
21 class Program
22 {
23 static void Main(string[] args)
24 {
25 PriceManager priceManager = new PriceManager();
26
27 //調用priceManager的GetPrice辦法獲取價錢
28 //直接調用委托的Invoke獲取價錢,兩者停止比擬
29 priceManager.GetPriceHandler = new PriceHandler(ComputerPrice);
30 Console.WriteLine(string.Format("GetPrice\n Computer's price is {0}!",
31 priceManager.GetPrice()));
32 Console.WriteLine(string.Format("Invoke\n Computer's price is {0}!",
33 priceManager.GetPriceHandler.Invoke()));
34
35 Console.WriteLine();
36
37 priceManager.GetPriceHandler = new PriceHandler(BookPrice);
38 Console.WriteLine(string.Format("GetPrice\n Book's price is {0}!",
39 priceManager.GetPrice()));
40 Console.WriteLine(string.Format("Invoke\n Book's price is {0}!" ,
41 priceManager.GetPriceHandler.Invoke()));
42
43 Console.ReadKey();
44 }
45 //書本價錢為98元
46 public static double BookPrice()
47 {
48 return 98.0;
49 }
50 //計算機價錢為8800元
51 public static double ComputerPrice()
52 {
53 return 8800.0;
54 }
55 }
復制代碼

運轉後果

察看運轉的後果,假如把委托對象 GetPriceHandler 設置為 public ,外界可以直接調用 GetPriceHandler.Invoke 獲取運轉後果而移除了 GetPrice 辦法的處置,這正是開發人員最不想看到的。
為了保證零碎的封裝性,開發往往需求把委托對象 GetPriceHandler 設置為 private, 再辨別參加 AddHandler,RemoveHandler 辦法對 GetPriceHandler 委托對象停止封裝。

復制代碼
 1     public delegate double PriceHandler();
2
3 public class PriceManager
4 {
5 private PriceHandler GetPriceHandler;
6
7 //委托處置,當價錢高於100元按8.8折計算,其他按原價計算
8 public double GetPrice()
9 {
10 if (GetPriceHandler!=null)
11 {
12 if (GetPriceHandler() > 100)
13 return GetPriceHandler()*0.88;
14 else
15 return GetPriceHandler();
16 }
17 return -1;
18 }
19
20 public void AddHandler(PriceHandler handler)
21 {
22 GetPriceHandler += handler;
23 }
24
25 public void RemoveHandler(PriceHandler handler)
26 {
27 GetPriceHandler -= handler;
28 }
29 }
30 ................
31 ................
復制代碼

為了保管封裝性,很多操作都需求參加AddHandler、RemoveHandler 這些類似的辦法代碼,這不免令人感到膩煩。
為了進一步簡化操作,事情這個概念應運而生。

4.2 事情的定義

事情(event)可被視作為一種特別的委托,它為委托對象隱式地樹立起add_XXX、remove_XXX 兩個辦法,用作注冊與登記事情的處置辦法。而且事情對應的變量成員將會被視為 private 變量,外界無法逾越事情所在對象直接訪問它們,這使事情具有良好的封裝性,而且免除了add_XXX、remove_XXX等繁瑣的代碼。

1     public class EventTest
2 {
3 public delegate void MyDelegate();
4 public event MyDelegate MyEvent;
5 }

察看事情的編譯進程可知,在編譯的時分,零碎為 MyEvent 事情自動樹立add_MyEvent、remove_MyEvent 辦法。

 

4.3 事情的運用方式

事情能經過+=和-=兩個方式注冊或許登記對其處置的辦法,運用+=與-=操作符的時分,零碎會自動調用對應的 add_XXX、remove_XXX 停止處置。
值得留意,在PersonManager類的Execute辦法中,假如 MyEvent 綁定的處置辦法不為空,即可運用MyEvent(string)引發事情。但假如在外界的 main 辦法中直接運用 personManager.MyEvent (string) 來引發事情,零碎將引發錯誤報告。這正是由於事情具有了良好的封裝性,使外界不能逾越事情所在的對象訪問其變量成員。

留意在事情所處的對象之外,事情只能呈現在+=,-=的左方。

 此時,開發人員無須手動添加 add_XXX、remove_XXX 的辦法,就可完成與4.1例子中的相反功用,完成了良好的封裝。

復制代碼
 1     public delegate void MyDelegate(string name);
2
3 public class PersonManager
4 {
5 public event MyDelegate MyEvent;
6
7 //執行事情
8 public void Execute(string name)
9 {
10 if (MyEvent != null)
11 MyEvent(name);
12 }
13 }
14
15 class Program
16 {
17 static void Main(string[] args)
18 {
19 PersonManager personManager = new PersonManager();
20 //綁定事情處置辦法
21 personManager.MyEvent += new MyDelegate(GetName);
22 personManager.Execute("Leslie");
23 Console.ReadKey();
24 }
25
26 public static void GetName(string name)
27 {
28 Console.WriteLine("My name is " + name);
29 }
30 }
復制代碼

 

4.4 事情處置辦法的綁定

在綁定事情處置辦法的時分,事情呈現在+=、-= 操作符的右邊,對應的委托對象呈現在+=、-= 操作符的左邊。對應以上例子,事情提供了更復雜的綁定方式,只需求在+=、-= 操作符的右方寫上辦法稱號,零碎就能自動辯認。

復制代碼
 1     public delegate void MyDelegate(string name);
2
3 public class PersonManager
4 {
5 public event MyDelegate MyEvent;
6 .........
7 }
8
9 class Program
10 {
11 static void Main(string[] args)
12 {
13 PersonManager personManager = new PersonManager();
14 //綁定事情處置辦法
15 personManager.MyEvent += GetName;
16 .............
17 }
18
19 public static void GetName(string name)
20 {.........}
21 }
復制代碼

假如覺得編寫 GetName 辦法過於費事,你還可以運用匿名辦法綁定事情的處置。

復制代碼
 1     public delegate void MyDelegate(string name);
2
3 public class PersonManager
4 {
5 public event MyDelegate MyEvent;
6
7 //執行事情
8 public void Execute(string name)
9 {
10 if (MyEvent != null)
11 MyEvent(name);
12 }
13
14 static void Main(string[] args)
15 {
16 PersonManager personManager = new PersonManager();
17 //運用匿名辦法綁定事情的處置
18 personManager.MyEvent += delegate(string name){
19 Console.WriteLine("My name is "+name);
20 };
21 personManager.Execute("Leslie");
22 Console.ReadKey();
23 }
24 }
復制代碼

 

4.5 C#控件中的事情

在C#控件中存在多個的事情,像Click、TextChanged、SelectIndexChanged 等等,很多都是經過 EventHandler 委托綁定事情的處置辦法的,EventHandler 可說是C#控件中最罕見的委托 。

public delegate void EventHandler (Object sender, EventArgs e)

EventHandler 委托並無前往值,sender 代表引發事情的控件對象,e 代表由該事情生成的數據 。在ASP.NET中可以直接經過btn.Click+=new EventHandler(btn_onclick) 的方式為控件綁定處置辦法。

復制代碼
 1 <html xmlns="http://www.w3.org/1999/xhtml">
2 <head runat="server">
3 <title></title>
4 <script type="text/C#" runat="server">
5 protected void Page_Load(object sender, EventArgs e)
6 {
7 btn.Click += new EventHandler(btn_onclick);
8 }
9
10 public void btn_onclick(object obj, EventArgs e)
11 {
12 Button btn = (Button)obj;
13 Response.Write(btn.Text);
14 }
15 </script>
16 </head>
17 <body>
18 <form id="form1" runat="server">
19 <div>
20 <asp:Button ID="btn" runat="server" Text="Button"/>
21 </div>
22 </form>
23 </body>
24 </html>
復制代碼

更多時分,只需求在頁面運用 OnClick=“btn_onclick" 辦法,在編譯的時分零碎就會自動對事情處置辦法停止綁定。

復制代碼
 1 <html xmlns="http://www.w3.org/1999/xhtml">
2 <head runat="server">
3 <title></title>
4 <script type="text/C#" runat="server">
5 public void btn_onclick(object obj, EventArgs e)
6 {
7 Button btn = (Button)obj;
8 Response.Write(btn.Text);
9 }
10 </script>
11 </head>
12 <body>
13 <form id="form1" runat="server">
14 <div>
15 <asp:Button ID="btn" runat="server" Text="Button" OnClick="btn_onclick"/>
16 </div>
17 </form>
18 </body>
19 </html>
復制代碼

 

EventHandler 只是 EventHandler<TEventArgs> 泛型委托的一個復雜例子。現實上,大家可以應用 EventHandler<TEventArgs> 結構出所需求的委托。

public delegate void EventHandler<TEventArgs> (Object sender, TEventArgs e)

在EventHandler<TEventArgs>中,sender代表事情源,e 代表派生自EventArgs類的事情參數。開發人員可以樹立派生自EventArgs的類,從中參加需求運用到的事情參數,然後樹立 EventHandler<TEventArgs> 委托。

上面的例子中,先樹立一個派生自EventArgs的類MyEventArgs作為事情參數,然後在EventManager中樹立事情myEvent , 經過 Execute 辦法可以激起事情。最後在測試中綁定 myEvent 的處置辦法 ShowMessage,在ShowMessage顯示myEventArgs 的事情參數 Message。

復制代碼
 1     public class MyEventArgs : EventArgs
2 {
3 private string args;
4
5 public MyEventArgs(string message)
6 {
7 args = message;
8 }
9
10 public string Message
11 {
12 get { return args; }
13 set { args = value; }
14 }
15 }
16
17 public class EventManager
18 {
19 public event EventHandler<MyEventArgs> myEvent;
20
21 public void Execute(string message)
22 {
23 if (myEvent != null)
24 myEvent(this, new MyEventArgs(message));
25 }
26 }
27
28 class Program
29 {
30 static void Main(string[] args)
31 {
32 EventManager eventManager = new EventManager();
33 eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage);
34 eventManager.Execute("How are you!");
35 Console.ReadKey();
36 }
37
38 public static void ShowMessage(object obj,MyEventArgs e)
39 {
40 Console.WriteLine(e.Message);
41 }
42 }
復制代碼

運轉後果

 

4.6 為用戶控件樹立事情

在ASP.NET開發中,頁面往往會呈現很多相似的控件與代碼,開發人員可以經過用戶控件來防止反復的代碼。但往往同一個用戶控件,在不同的頁面中需求有不同的呼應。此時為用戶控件樹立事情,便可輕松地處理此問題。
上面例子中,在用戶控件 MyControl 中樹立存在一個GridView控件,GridView 控件經過 GetPersonList 辦法獲取數據源。在用戶控件中還定義了 RowCommand 事情,在 GridView 的 GridView_RowCommand 辦法中激起此事情。這樣,在頁面運用此控件時,開發人員就可以定義不同的辦法處置 RowCommand 事情。

復制代碼
 1 public class Person
2 {
3 public int ID
4 { get; set; }
5 public string Name
6 { get; set; }
7 public int Age
8 { get; set; }
9 }
10
11 <!-- 用戶控件 -->
12 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %>
13 <script type="text/C#" runat="server">
14 protected void Page_Load(object sender, EventArgs e)
15 {
16 GridView1.DataSource = GetPersonList();
17 GridView1.DataBind();
18 }
19
20 //綁定數據源
21 protected IList<Person> GetPersonList()
22 {
23 IList<Person> list = new List<Person>();
24 Person person1 = new Person();
25 person1.ID = 1;
26 person1.Name = "Leslie";
27 person1.Age = 29;
28 list.Add(person1);
29 ...........
30 return list;
31 }
32
33 public event GridViewCommandEventHandler RowCommand;
34
35 protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
36 {
37 if (RowCommand != null)
38 RowCommand(sender, e);
39 }
40 </script>
41 <div>
42 <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
43 onrowcommand="GridView1_RowCommand">
44 <Columns>
45 <asp:BoundField DataField="ID" HeaderText="ID"/>
46 <asp:BoundField DataField="Name" HeaderText="Name"/>
47 <asp:BoundField DataField="Age" HeaderText="Age"/>
48 <asp:ButtonField CommandName="Get" Text="Select"/>
49 </Columns>
50 </asp:GridView>
51 </div>
52
53 <!-- 頁面代碼 -->
54 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
55 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %>
56 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
57
58 <html xmlns="http://www.w3.org/1999/xhtml">
59 <head runat="server">
60 <title></title>
61 <script type="text/C#" runat="server">
62 protected void myControl_RowCommand(object sender, GridViewCommandEventArgs e)
63 {
64 if (e.CommandName == "Get")
65 {
66 GridView gridView=(GridView)sender;
67 int index = int.Parse(e.CommandArgument.ToString());
68 label.Text=gridView.Rows[index].Cells[1].Text;
69 }
70 }
71 </script>
72 </head>
73 <body>
74 <form id="form1" runat="server">
75 <div>
76 <ascx:myControl ID="myControl" runat="server" OnRowCommand="myControl_RowCommand"></ascx:myControl>
77 <br />
78 Select Name : <asp:Label ID="label" runat="server"></asp:Label><br />
79 </div>
80 </form>
81 </body>
82 </html>
復制代碼

運轉後果

 

運用控件已有的事情固然復雜,但它限制了傳送的參數類型,使開發人員無法傳送額定的自定義參數。在構造比擬復雜的用戶控件中,運用已有的控件事情,顯然不夠方便,此時,您可以思索為用戶控件樹立自定義事情。
首先用戶控件中包括訂單信息與訂單明細列表,首先定義一個事情參數 MyEventArgs,外面包括了訂單信息與一個 OrderItem 數組。然後樹立用戶控件的委托MyDelegate 與對應的事情 MyEvent,在 Button 的 Click 事情中激起 MyEvent 自定義事情。這樣在頁面處置辦法 myControl_Click 中就可以經過事情參數 MyEventArgs 獲取用戶控件中的屬性,計算訂單的總體價錢。

復制代碼
  1 <!--   根底類    -->
2 public class OrderItem
3 {
4 public OrderItem(string id,string goods,double price,int count)
5 {
6 this.OrderItemID = id; //明細單ID
7 this.Goods = goods; //商品稱號
8 this.Price = price; //商品單價
9 this.Count = count; //商品數量
10 }
11
12 public string OrderItemID
13 { get; set; }
14 public string Goods
15 { get; set; }
16 public double Price
17 { get; set; }
18 public int Count
19 { get; set; }
20 }
21
22 /// 事情參數
23 public class MyEventArgs:EventArgs
24 {
25 public MyEventArgs(string name,string address,string tel,
26 string orderCode,IList<OrderItem> orderItemList)
27 {
28 Name = name; //買家姓名
29 Address = address; //買家地址
30 Tel = tel; //買家電話
31 OrderCode = orderCode; //訂單號碼
32 OrderItemList = orderItemList; //訂單明細
33 }
34
35 public string Name
36 { get;set; }
37 public string Address
38 { get; set; }
39 public string Tel
40 { get; set; }
41 public string OrderCode
42 { get; set; }
43 public IList<OrderItem> OrderItemList
44 { get; set; }
45 }
46
47 <!-- 用戶控件 -->
48 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %>
49 <script type="text/C#" runat="server">
50 protected void Page_Load(object sender, EventArgs e)
51 {
52 GridView1.DataSource = GetList();
53 GridView1.DataBind();
54 }
55
56 //模仿數據源
57 protected IList<OrderItem> GetList()
58 {
59 IList<OrderItem> list = new List<OrderItem>();
60 OrderItem orderItem = new OrderItem("1", "Asus N75S", 8800, 2);
61 list.Add(orderItem);
62 ..........
63 return list;
64 }
65
66 //自定義委托
67 public delegate void MyDelegate(object sender,MyEventArgs myEventArgs);
68 //自定義事情
69 public event MyDelegate MyEvent;
70
71 //按下Button時激起自定義事情
72 protected void btn_click(object sender, EventArgs e)
73 {
74 if (MyEvent != null)
75 {
76 MyEventArgs myEventArgs = new MyEventArgs(labelName.Text, labelAddress.Text, labelTel.Text
77 , labelOrderCode.Text, GetList());
78 MyEvent(this,myEventArgs);
79 }
80 }
81 </script>
82 <div>
83 Name : <asp:Label ID="labelName" runat="server">Leslie</asp:Label><br />
84 Address : <asp:Label ID="labelAddress" runat="server">ZhongShan University 2A 501</asp:Label><br />
85 Tel : <asp:Label ID="labelTel" runat="server">13660123456</asp:Label><br />
86 Order Code : <asp:Label ID="labelOrderCode" runat="server">A12012031223B0030</asp:Label><br /><br />
87 <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="5">
88 <Columns>
89 <asp:BoundField DataField="OrderItemID" HeaderText="ID"/>
90 <asp:BoundField DataField="Goods" HeaderText="Goods"/>
91 <asp:BoundField DataField="Price" HeaderText="Price"/>
92 <asp:BoundField DataField="Count" HeaderText="Count"/>
93 </Columns>
94 </asp:GridView>
95 <br />
96 <asp:Button ID="btn" runat="server" Text="Account" OnClick="btn_click"/>
97 </div>
98
99 <!-- 頁面處置 -->
100 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
101 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %>
102 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
103
104 <html xmlns="http://www.w3.org/1999/xhtml">
105 <head runat="server">
106 <title></title>
107 <script type="text/C#" runat="server">
108 //在頁面定義用戶控件MyEvent事情的處置辦法
109 protected void myControl_Click(object sender,MyEventArgs e)
110 {
111 //計算訂單總體價錢
112 double totalPrice=0;
113 IList<OrderItem> list=e.OrderItemList;
114 foreach(OrderItem item in list)
115 totalPrice+=item.Price*item.Count;
116 //展現訂單號及總體費用
117 labelOrderCode.Text = e.OrderCode;
118 labelTotalPrice.Text = totalPrice.ToString();
119 }
120 </script>
121 </head>
122 <body>
123 <form id="form1" runat="server">
124 <div>
125 <ascx:myControl ID="myControl" runat="server" OnMyEvent="myControl_Click"></ascx:myControl>
126 <br />
127 OrderCode : <asp:Label ID="labelOrderCode" runat="server"></asp:Label><br />
128 TotalPrice : <asp:Label ID="labelTotalPrice" runat="server"></asp:Label>
129 </div>
130 </form>
131 </body>
132 </html>
復制代碼

運轉後果


若對自定義事情不太熟習的冤家很多時分會運用 UserControl.FindControl 的方式獲取用戶控件中的屬性,但當你深化理解自定義事情的開發進程當前,就能無效簡化開發的進程。


回到目錄

五、Lambda 表達式

5.1 Lambda 的意義

在Framework 2.0 以前,聲明委托的獨一辦法是經過辦法命名,從Framework 2.0 起,零碎開端支持匿名辦法。
經過匿名辦法,可以直接把一段代碼綁定給事情,因而增加了實例化委托所需的編碼零碎開支。
而在 Framework 3.0 開端,Lambda 表達式開端逐步取代了匿名辦法,作為編寫內聯代碼的首選方式。總體來說,Lambda 表達式的作用是為了運用更復雜的方式來編寫匿名辦法,徹底簡化委托的運用方式。

 

5.2 回憶匿名辦法的運用

匿名辦法的運用曾經在4.4節復雜引見過,在此回憶一下。 
運用上面的方式,可以經過匿名辦法為Button的Click事情綁定處置辦法。

復制代碼
1         static void Main(string[] args)
2 {
3 Button btn = new Button();
4 btn.Click+=delegate(object obj,EventArgs e){
5 MessageBox.Show("Hello World !");
6 };
7 }
復制代碼

總是運用 delegate(){......} 的方式樹立匿名辦法,令人不由覺得郁悶。於是從Framework 3.0 起, Lambda 表達式開端呈現。

 

5.3 復雜引見泛型委托

在引見 Lambda 表達式前,先引見一下常用的幾個泛型委托。

 

5.3.1 泛型委托 Predicate<T>

早在Framework 2.0 的時分,微軟就為 List<T> 類添加了 Find、FindAll 、ForEach 等辦法用作數據的查找。

public T Find ( Predicate<T> match)
public List<T> FindAll(Predicate<T>  match)

在這些辦法中存在一個Predicate <T> 表達式,它是一個前往bool的泛型委托,能承受一個恣意類型的對象作為參數。

public delegate bool Predicate<T>(T obj)

在上面例子中,Predicate 委托綁定了參數為Person類的辦法Match作為查詢條件,然後運用 FindAll 辦法查找到適宜條件的 List<Person> 集合。

復制代碼
 1     class Program
2 {
3 static void Main(string[] args)
4 {
5 List<Person> list = GetList();
6 //綁定查詢條件
7 Predicate<Person> predicate = new Predicate<Person>(Match);
8 List<Person> result = list.FindAll(predicate);
9 Console.WriteLine(“Person count is : ” + result.Count);
10 Console.ReadKey();
11 }
12 //模仿源數據
13 static List<Person> GetList()
14 {
15 var personList = new List<Person>();
16 var person1 = new Person(1,"Leslie",29);
17 personList.Add(person1);
18 ........
19 return personList;
20 }
21 //查詢條件
22 static bool Match(Person person)
23 {
24 return person.Age <= 30;
25 }
26 }
27
28 public class Person
29 {
30 public Person(int id, string name, int age)
31 {
32 ID = id;
33 Name = name;
34 Age = age;
35 }
36
37 public int ID
38 { get; set; }
39 public string Name
40 { get; set; }
41 public int Age
42 { get; set; }
43 }
復制代碼

 

5.3.2 泛型委托 Action

Action<T> 的運用方式與 Predicate<T> 類似,不同之處在於 Predicate<T> 前往值為 bool ,  Action<T> 的前往值為 void。
Action 支持0~16個參數,可以按需求恣意運用。

public delegate void Action()
public delegate void Action<T1>(T1 obj1)
public delegate void Action<T1,T2> (T1 obj1, T2 obj2)
public delegate void Action<T1,T2,T3> (T1 obj1, T2 obj2,T3 obj3)
............
public delegate void Action<T1,T2,T3,......,T16> (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)

復制代碼
 1         static void Main(string[] args)
2 {
3 Action<string> action=ShowMessage;
4 action("Hello World");
5 Console.ReadKey();
6 }
7
8 static void ShowMessage(string message)
9 {
10 MessageBox.Show(message);
11 }
復制代碼

 

5.3.3 泛型委托 Func

委托 Func 與 Action 類似,異樣支持 0~16 個參數,不同之處在於Func 必需具有前往值

public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 obj1)
public delegate TResult Func<T1,T2,TResult>(T1 obj1,T2 obj2)
public delegate TResult Func<T1,T2,T3,TResult>(T1 obj1,T2 obj2,T3 obj3)
............
public delegate TResult Func<T1,T2,T3,......,T16,TResult>(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)

復制代碼
 1         static void Main(string[] args)
2 {
3 Func<double, bool, double> func = Account;
4 double result=func(1000, true);
5 Console.WriteLine("Result is : "+result);
6 Console.ReadKey();
7 }
8
9 static double Account(double a,bool condition)
10 {
11 if (condition)
12 return a * 1.5;
13 else
14 return a * 2;
15 }
復制代碼

 

5.4 揭開 Lambda 奧秘的面紗

Lambda 的表達式的編寫格式如下:

x=> x * 1.5

當中 “ => ” 是 Lambda 表達式的操作符,在右邊用作定義一個參數列表,左邊可以操作這些參數。

例子一, 先把 int x 設置 1000,經過 Action 把表達式定義為 x=x+500 ,最後經過 Invoke 激起委托。

復制代碼
1         static void Main(string[] args)
2 {
3 int x = 1000;
4 Action action = () => x = x + 500;
5 action.Invoke();
6
7 Console.WriteLine("Result is : " + x);
8 Console.ReadKey();
9 }
復制代碼


例子二,經過 Action<int> 把表達式定義 x=x+500, 到最後輸出參數1000,失掉的後果與例子一相反。
留意,此處Lambda表達式定義的操作運用 { } 括弧包括在一同,外面可以包括一系列的操作。

復制代碼
 1         static void Main(string[] args)
2 {
3 Action<int> action = (x) =>
4 {
5 x = x + 500;
6 Console.WriteLine("Result is : " + x);
7 };
8 action.Invoke(1000);
9 Console.ReadKey();
10 }
復制代碼

 

例子三,定義一個Predicate<int>,當輸出值大約等於1000則前往 true , 否則前往 false。與5.3.1的例子相比,Predicate<T>的綁定不需求顯式樹立一個辦法,而是直接在Lambda表達式裡完成,簡約方便了不少。

復制代碼
 1         static void Main(string[] args)
2 {
3 Predicate<int> predicate = (x) =>
4 {
5 if (x >= 1000)
6 return true;
7 else
8 return false;
9 };
10 bool result=predicate.Invoke(500);
11 Console.ReadKey();
12 }
復制代碼

 

例子四,在計算商品的價錢時,當商品分量超越30kg則打9折,其他按原價處置。此時可以運用Func<double,int,double>,參數1為商品原價,參數2為商品分量,最後前往值為 double 類型。

復制代碼
 1         static void Main(string[] args)
2 {
3 Func<double, int, double> func = (price, weight) =>
4 {
5 if (weight >= 30)
6 return price * 0.9;
7 else
8 return price;
9 };
10 double totalPrice = func(200.0, 40);
11 Console.ReadKey();
12 }
復制代碼


例子五,運用Lambda為Button定義Click事情的處置辦法。與5.2的例子相比,運用Lambda比運用匿名辦法愈加復雜。

復制代碼
1         static void Main(string[] args)
2 {
3 Button btn = new Button();
4 btn.Click += (obj, e) =>
5 {
6 MessageBox.Show("Hello World!");
7 };
8 Console.ReadKey();
9 }
復制代碼


例子六,此處運用5.3.1的例子,在List<Person>的FindAll辦法中直接運用Lambda表達式。
相比之下,運用Lambda表達式,不需求定義Predicate<T>對象,也不需求顯式設定綁定辦法,簡化了不工序。

復制代碼
 1      class Program
2 {
3 static void Main(string[] args)
4 {
5 List<Person> personList = GetList();
6
7 //查找年齡少於30年的人
8 List<Person> result=personList.FindAll((person) => person.Age =< 30);
9 Console.WriteLine("Person count is : " + result.Count);
10 Console.ReadKey();
11 }
12
13 //模仿源數據
14 static List<Person> GetList()
15 {
16 var personList = new List<Person>();
17 var person1 = new Person(1,"Leslie",29);
18 personList.Add(person1);
19 .......
20 return personList;
21 }
22 }
23
24 public class Person
25 {
26 public Person(int id, string name, int age)
27 {
28 ID = id;
29 Name = name;
30 Age = age;
31 }
32
33 public int ID
34 { get; set; }
35 public string Name
36 { get; set; }
37 public int Age
38 { get; set; }
39 }
復制代碼


當在運用LINQ技術的時分,四處都會洋溢著 Lambda 的身影,此時更能表現 Lambda 的優點。
但 LINQ 觸及到分部類,分部辦法,IEnumerable<T>,迭代器等多方面的知識,這些曾經超出本章的引見范圍。
經過這一節的引見,希望可以協助大家更深化地理解 Lambda 的運用。

 回到目錄

本章小結

本章次要引見了委托(Delegate)的運用,委托對象是一個派生自 System.MultcastDelegate 的類,它能經過 Invoke 方式停止同步伐用,也可以經過 BeginInvoke,EndInvoke 方式完成異步伐用。而事情(Event)屬於一種特殊的委托,它與委托類型同步運用,可以簡化的開發進程。
最後,本文還引見了匿名辦法的運用方式,以及 Lambda 表達式的由來。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved