技术开发 频道

C#实战揭秘:深入解析委托与事件的由来

        【IT168 技术】本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单。

  还将为您解释委托的协变与逆变,以及如何使用 Delegate 使 Observer(观察者)模式的使用变得更加简单。

  在事件的介绍上,会讲述事件的使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用。

  最后一节,将介绍Predicate、Action、Func多种泛型委托的使用和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 workerList = new List();

  
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 }

 

${PageNumber}

  最后运行结果与上面的例子相同。

  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 所输入的参数为 object 类型,其绑定方法 GetMessage 的参数也必须为 object 。否则,即使绑定方法的参数为 object 的子类,系统也无法辨认。

  3.6 泛型委托

  委托逆变虽然实用,但如果都以 object 作为参数,则需要每次都对参数进行类型的判断,这不禁令人感到厌烦。

  为此,泛型委托应运而生,泛型委托有着委托逆变的优点,同时利用泛型的特性,可以使一个委托绑定多个不同类型参数的方法,而且在方法中不需要使用 is 进行类型判断,从而简化了代码。

 1 class Program

  
2 {

  
3 public delegate void Handler(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 workerHander = new Handler(GetWorkerWages);

  
18 Worker worker = new Worker();

  
19 worker.Wages = 3000;

  
20 workerHander(worker);

  
21

  
22 Handler managerHandler = new Handler(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 }

 

  运行结果

C#实战揭秘:深入解析委托与事件的由来

  观察运行的结果,如果把委托对象 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 方法。

C#实战揭秘:深入解析委托与事件的由来

  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 }

 

 

${PageNumber}

 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 泛型委托的一个简单例子。事实上,大家可以利用 EventHandler 构造出所需要的委托。

  public delegate void EventHandler (Object sender, TEventArgs e)

  在EventHandler中,sender代表事件源,e 代表派生自EventArgs类的事件参数。开发人员可以建立派生自EventArgs的类,从中加入需要使用到的事件参数,然后建立 EventHandler 委托。

  下面的例子中,先建立一个派生自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 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(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 }

 

  运行结果

C#实战揭秘:深入解析委托与事件的由来

  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>

 

   运行结果

C#实战揭秘:深入解析委托与事件的由来

  使用控件已有的事件固然简单,但它限制了传送的参数类型,使开发人员无法传送额外的自定义参数。在结构比较复杂的用户控件中,使用已有的控件事件,显然不够方便,此时,您可以考虑为用户控件建立自定义事件。

  首先用户控件中包含订单信息与订单明细列表,首先定义一个事件参数 MyEventArgs,里面包含了订单信息与一个 OrderItem 数组。然后建立用户控件的委托MyDelegate 与对应的事件 MyEvent,在 Button 的 Click 事件中激发 MyEvent 自定义事件。这样在页面处理方法 myControl_Click 中就可以通过事件参数 MyEventArgs 获取用户控件中的属性,计算订单的总体价格。

   

  若对自定义事件不太熟悉的朋友很多时候会使用 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

  早在Framework 2.0 的时候,微软就为 List 类添加了 Find、FindAll 、ForEach 等方法用作数据的查找。

  public T Find ( Predicate match)

  public List FindAll(Predicate match)

  在这些方法中存在一个Predicate 表达式,它是一个返回bool的泛型委托,能接受一个任意类型的对象作为参数。

  public delegate bool Predicate(T obj)

  在下面例子中,Predicate 委托绑定了参数为Person类的方法Match作为查询条件,然后使用 FindAll 方法查找到合适条件的 List 集合。

 

 1 class Program

  
2 {

  
3 static void Main(string[] args)

  
4 {

  
5 List list = GetList();

  
6 //绑定查询条件

  
7 Predicate predicate = new Predicate(Match);

  
8 List result = list.FindAll(predicate);

  
9 Console.WriteLine(“Person count is : ” + result.Count);

  
10 Console.ReadKey();

  
11 }

  
12 //模拟源数据

  
13 static List GetList()

  
14 {

  
15 var personList = new List();

  
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 的使用方式与 Predicate 相似,不同之处在于 Predicate 返回值为 bool , Action 的返回值为 void。

  Action 支持0~16个参数,可以按需求任意使用。

 public delegate void Action()

  
public delegate void Action(T1 obj1)

  
public delegate void Action (T1 obj1, T2 obj2)

  
public delegate void Action (T1 obj1, T2 obj2,T3 obj3)

  ............

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

  
1 static void Main(string[] args)

  
2 {

  
3 Action 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()

  
public delegate TResult Func(T1 obj1)

  
public delegate TResult Func(T1 obj1,T2 obj2)

  
public delegate TResult Func(T1 obj1,T2 obj2,T3 obj3)

  ............

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

  
1 static void Main(string[] args)

  
2 {

  
3 Func 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 把表达式定义 x=x+500, 到最后输入参数1000,得到的结果与例子一相同。

  注意,此处Lambda表达式定义的操作使用 { } 括弧包括在一起,里面可以包含一系列的操作。

  1 static void Main(string[] args)

  
2 {

  
3 Action action = (x) =>

  
4 {

  
5 x = x + 500;

  
6 Console.WriteLine("Result is : " + x);

  
7 };

  
8 action.Invoke(1000);

  
9 Console.ReadKey();

  
10 }

 

  复制代码

  例子三,定义一个Predicate,当输入值大约等于1000则返回 true , 否则返回 false。与5.3.1的例子相比,Predicate的绑定不需要显式建立一个方法,而是直接在Lambda表达式里完成,简洁方便了不少。

 1 static void Main(string[] args)

  
2 {

  
3 Predicate 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,参数1为商品原价,参数2为商品重量,最后返回值为 double 类型。 

 1 static void Main(string[] args)

  
2 {

  
3 Func 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的FindAll方法中直接使用Lambda表达式。

  相比之下,使用Lambda表达式,不需要定义Predicate对象,也不需要显式设定绑定方法,简化了不工序。

 1 class Program

  
2 {

  
3 static void Main(string[] args)

  
4 {

  
5 List personList = GetList();

  
6

  
7 //查找年龄少于30年的人

  
8 List result=personList.FindAll((person) => person.Age =< 30);

  
9 Console.WriteLine("Person count is : " + result.Count);

  
10 Console.ReadKey();

  
11 }

  
12

  
13 //模拟源数据

  
14 static List GetList()

  
15 {

  
16 var personList = new List();

  
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,迭代器等多方面的知识,这些已经超出本章的介绍范围。

  本章主要介绍了委托(Delegate)的使用,委托对象是一个派生自 System.MultcastDelegate 的类,它能通过 Invoke 方式进行同步调用,也可以通过 BeginInvoke,EndInvoke 方式实现异步调用。而事件(Event)属于一种特殊的委托,它与委托类型同步使用,可以简化的开发过程。

0
相关文章