找回密码
 立即注册
首页 业界区 业界 C#综合揭秘——深入分析委托与事件

C#综合揭秘——深入分析委托与事件

些耨努 2025-5-29 18:59:49
引言

本篇文章将为你介绍一下 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 等三个常用方法。
1.jpeg

Invoke 方法是用于同步调用委托对象的对应方法,而BeginInvoke、EndInvoke是用于以异步方式调用对应方法的。
对于异步调用的使用方式,可以参考:C#综合揭秘——细说多线程
  1. 1      public class MyDelegate:MulticastDelegate<br>2      {<br>3          //同步调用委托方法<br>4          public virtual void Invoke();<br>5          //异步调用委托方法<br>6          public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state);<br>7          public virtual void EndInvoke(IAsyncResult result);<br>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. 1     class Program<br> 2     {<br> 3         delegate void MyDelegate(string message);<br> 4 <br> 5         public class Example<br> 6         {<br> 7             public void Method(string message)<br> 8             {<br> 9                 MessageBox.Show(message);<br>10             }<br>11         }<br>12 <br>13         static void Main(string[] args)<br>14         {<br>15             Example example=new Example();<br>16             MyDelegate myDelegate=new MyDelegate(example.Method);<br>17             myDelegate("Hello World");<br>18             Console.ReadKey();<br>19         }<br>20     }
复制代码
 
3.2 带返回值的委托
当建立委托对象时,委托的返回值必须与委托方法相对应。使用下面的例子,方法将返回 “Hello Leslie” 。
  1. 1     class Program<br> 2     {<br> 3         delegate string MyDelegate(string message);<br> 4 <br> 5         public class Example<br> 6         {<br> 7             public string Method(string name)<br> 8             {<br> 9                 return "Hello " + name;<br>10             }<br>11         }<br>12 <br>13         static void Main(string[] args)<br>14         {<br>15             Example example=new Example();<br>16             //绑定委托方法<br>17             MyDelegate myDelegate=new MyDelegate(example.Method);<br>18             //调用委托,获取返回值<br>19             string message = myDelegate("Leslie");<br>20             Console.WriteLine(message);<br>21             Console.ReadKey();<br>22         }<br>23     }
复制代码
 
3.3 多路广播委托
在第二节前曾经提过,委托类继承于MulticastDelegate,这使委托对象支持多路广播,即委托对象可以绑定多个方法。当输入参数后,每个方法会按顺序进行迭代处理,并返回最后一个方法的计算结果。
下面的例子中,Price 类中有两个计算方法,Ordinary 按普通的9.5折计算,Favourable 按优惠价 8.5 折计算。委托同时绑定了这两个方法,在输入参数100以后,Ordinary、Favourable这两个方法将按顺序迭代执行下去,最后返回 Favourable 方法的计算结果 85。
  1. 1         delegate double MyDelegate(double message);<br> 2 <br> 3         public class Price<br> 4         {<br> 5             public double Ordinary(double price)<br> 6             {<br> 7                 double price1 = 0.95 * price;<br> 8                 Console.WriteLine("Ordinary Price : "+price1);<br> 9                 return price1;<br>10             }<br>11 <br>12             public double Favourable(double price)<br>13             {<br>14                 double price1 = 0.85 * price;<br>15                 Console.WriteLine("Favourable Price : " + price1);<br>16                 return price1;<br>17             }<br>18 <br>19             static void Main(string[] args)<br>20             {<br>21                 Price price = new Price();<br>22                 //绑定Ordinary方法<br>23                 MyDelegate myDelegate = new MyDelegate(price.Ordinary);<br>24                 //绑定Favourable方法<br>25                 myDelegate += new MyDelegate(price.Favourable);<br>26                 //调用委托<br>27                 Console.WriteLine("Current Price : " + myDelegate(100));<br>28                 Console.ReadKey();<br>29             }<br>30         }
复制代码
运行结果
2.jpeg


3.4 浅谈Observer模式
回顾一下简单的 Observer 模式,它使用一对多的方式,可以让多个观察者同时关注同一个事物,并作出不同的响应。
例如下面的例子,Manager的底薪为基本工资的1.5倍,Assistant的底薪为基本工资的1.2倍。WageManager类的RegisterWorker方法与RemoveWorker方法可以用于注册和注销观察者,最后执行Execute方法可以对多个已注册的观察者同时输入参数。
 
3.jpeg
 
  1. 1     public class WageManager<br> 2     {<br> 3         IList<Worker> workerList = new List<Worker>();<br> 4         <br> 5         public void RegisterWorker(Worker worker)<br> 6         {<br> 7             workerList.Add(worker);<br> 8         }<br> 9 <br>10         public void RemoveWorker(Worker worker)<br>11         {<br>12             workerList.Remove(worker);<br>13         }<br>14 <br>15         public void Excute(double basicWages)<br>16         {<br>17             if (workerList.Count != 0)<br>18                 foreach (var worker in workerList)<br>19                     worker.GetWages(basicWages);<br>20         }<br>21 <br>22         static void Main(string[] args)<br>23         {<br>24             WageManager wageManager = new WageManager();<br>25             //注册观察者<br>26             wageManager.RegisterWorker(new Manager());<br>27             wageManager.RegisterWorker(new Assistant());<br>28             //同时输入底薪3000元,分别进行计算<br>29             wageManager.Excute(3000);<br>30 <br>31             Console.ReadKey();<br>32         }<br>33     }<br>34 <br>35     public abstract class Worker<br>36     {<br>37         public abstract double GetWages(double basicWages);<br>38     }<br>39 <br>40     public class Manager:Worker<br>41     {<br>42          //Manager实际工资为底薪1.5倍<br>43         public override double GetWages(double basicWages)<br>44         {<br>45             double totalWages = 1.5 * basicWages;<br>46             Console.WriteLine("Manager's wages is " + totalWages);<br>47             return totalWages;<br>48         }<br>49     }<br>50 <br>51     public class Assistant : Worker<br>52     {<br>53         //Assistant实际工资为底薪的1.2倍<br>54         public override double GetWages(double basicWages)<br>55         {<br>56             double totalWages = 1.2 * basicWages;<br>57             Console.WriteLine("Assistant's wages is " + totalWages);<br>58             return totalWages;<br>59         }<br>60     }
复制代码
运行结果
4.jpeg

 
开发 Observer 模式时借助委托,可以进一步简化开发的过程。由于委托对象支持多路广播,所以可以把Worker类省略。在WageManager类中建立了一个委托对象wageHandler,通过Attach与Detach方法可以分别加入或取消委托。如果观察者想对事物进行监测,只需要加入一个委托对象即可。记得在第二节曾经提过,委托的GetInvodationList方法能获取多路广播委托列表,在Execute方法中,就是通过去多路广播委托列表去判断所绑定的委托数量是否为0。
  1. 1         public delegate double Handler(double basicWages);<br> 2  <br> 3          public class Manager<br> 4          {<br> 5              public double GetWages(double basicWages)<br> 6              {<br> 7                  double totalWages=1.5 * basicWages;<br> 8                  Console.WriteLine("Manager's wages is : " + totalWages);<br> 9                  return totalWages;<br>10              }<br>11          }<br>12  <br>13          public class Assistant<br>14          {<br>15              public double GetWages(double basicWages)<br>16              {<br>17                  double totalWages = 1.2 * basicWages;<br>18                  Console.WriteLine("Assistant's wages is : " + totalWages);<br>19                  return totalWages;<br>20              }<br>21          }<br>22  <br>23          public class WageManager<br>24          {<br>25              private Handler wageHandler;<br>26  <br>27              //加入观察者<br>28              public void Attach(Handler wageHandler1)<br>29              {<br>30                  wageHandler += wageHandler1;<br>31              }<br>32  <br>33              //删除观察者<br>34              public void Detach(Handler wageHandler1)<br>35              {<br>36                  wageHandler -= wageHandler1;<br>37              }<br>38  <br>39              //通过GetInvodationList方法获取多路广播委托列表,如果观察者数量大于0即执行方法<br>40              public void Execute(double basicWages)<br>41              {<br>42                  if (wageHandler!=null)<br>43                     if(wageHandler.GetInvocationList().Count() != 0)<br>44                         wageHandler(basicWages);<br>45              }<br>46  <br>47              static void Main(string[] args)<br>48              {<br>49                  WageManager wageManager = new WageManager();<br>50                  //加入Manager观察者<br>51                  Manager manager = new Manager();<br>52                  Handler managerHandler = new Handler(manager.GetWages);<br>53                  wageManager.Attach(managerHandler);<br>54  <br>55                  //加入Assistant观察者<br>56                  Assistant assistant = new Assistant();<br>57                  Handler assistantHandler = new Handler(assistant.GetWages);<br>58                  wageManager.Attach(assistantHandler);<br>59  <br>60                  //同时加入底薪3000元,分别进行计算<br>61                  wageManager.Execute(3000);<br>62                  Console.ReadKey();<br>63              }<br>64          }
复制代码
最后运行结果与上面的例子相同。
 
3.5 委托的协变与逆变
在 Framework 2.0 出现之前,委托协变这个概念还没有出现。此时因为委托是安全类型,它们不遵守继承的基础规则。即会这下面的情况:Manager 虽然是 Worker 的子类,但 GetWorkerHander 委托不能直接绑定 GetManager 方法,因为在委托当中它们的返回值 Manager 与 Worker 被视为完全无关的两个类型。
  1. 1      public class Worker<br> 2      {.......}<br> 3      public class Manager:Worker<br> 4      {.......}<br> 5  <br> 6       class Program<br> 7      {<br> 8          public delegate Worker GetWorkerHandler(int id);<br> 9          public delegate Manager GetManagerHandler(int id);<br>10  <br>11          public static Worker GetWorker(int id)<br>12          {<br>13              Worker worker = new Worker();<br>14              ..............<br>15              return worker;<br>16          }<br>17  <br>18          public static Manager GetManager(int id)<br>19          {<br>20              Manager manager = new Manager();<br>21              ..............<br>22              return manager;<br>23          }<br>24  <br>25          static void Main(string[] args)<br>26          {<br>27              GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);<br>28              var worker=workerHandler(1);<br>29  <br>30              GetManagerHandler managerHandler = new GetManagerHandler(GetManager);<br>31              var manager = managerHandler(2);<br>32              Console.ReadKey();<br>33          }<br>34      }
复制代码
自从Framework 2.0 面试以后,委托协变的概念就应运而生,此时委托可以按照传统的继承规则进行转换。即 GetWorkerHandler 委托可以直接绑定 GetManager 方法。
  1. 1      public class Worker<br> 2      {.......}<br> 3      public class Manager:Worker<br> 4      {.......}<br> 5  <br> 6       class Program<br> 7      {<br> 8          public delegate Worker GetWorkerHandler(int id);<br> 9          //在 Framework2.0 以上,委托 GetWorkerHandler 可绑定 GetWorker 与 GetManager 两个方法<br >10  <br>11          public static Worker GetWorker(int id)<br>12          {<br>13              Worker worker = new Worker();<br>14              return worker;<br>15          }<br>16  <br>17          public static Manager GetManager(int id)<br>18          {<br>19              Manager manager = new Manager();<br>20              return manager;<br>21          }<br>22  <br>23         static void Main(string[] args)<br>24         {<br>25             GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);<br>26             Worker worker=workerHandler(1);<br>27             GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager);<br>28             Manager manager = managerHandler(2) as Manager;<br>29             Console.ReadKey();<br>30         }<br>31      }
复制代码
委托逆变,是指委托方法的参数同样可以接收 “继承” 这个传统规则。像下面的例子,以 object 为参数的委托,可以接受任何 object 子类的对象作为参数。最后可以在处理方法中使用 is 对输入数据的类型进行判断,分别处理对不同的类型的对象。
  1. 1     class Program<br> 2     {<br> 3         public delegate void Handler(object obj);<br> 4 <br> 5         public static void GetMessage(object message)<br> 6         {<br> 7             if (message is string)<br> 8                 Console.WriteLine("His name is : " + message.ToString());<br> 9             if (message is int)<br>10                 Console.WriteLine("His age is : " + message.ToString());<br>11         }<br>12 <br>13         static void Main(string[] args)<br>14         {<br>15             Handler handler = new Handler(GetMessage);<br>16             handler(29);<br>17             Console.ReadKey();<br>18         }<br>19    }
复制代码
运行结果
5.jpeg

注意委托与其绑定方法的参数必须一至,即当 Handler 所输入的参数为 A 类型,其绑定方法 GetMessage 的参数也必须为 A 类或者 A 的父类 。相反,当绑定方法的参数为 A 的子类,系统也无法辨认。

 
3.6 泛型委托
委托逆变虽然实用,但如果都以 object 作为参数,则需要每次都对参数进行类型的判断,这不禁令人感到厌烦。
为此,泛型委托应运而生,泛型委托有着委托逆变的优点,同时利用泛型的特性,可以使一个委托绑定多个不同类型参数的方法,而且在方法中不需要使用 is 进行类型判断,从而简化了代码。
  1. 1     class Program<br> 2     {<br> 3         public delegate void Handler<T>(T obj);<br> 4 <br> 5         public static void GetWorkerWages(Worker worker)<br> 6         {<br> 7             Console.WriteLine("Worker's total wages is " + worker.Wages);<br> 8         }<br> 9 <br>10         public static void GetManagerWages(Manager manager)<br>11         {<br>12             Console.WriteLine("Manager's total wages is "+manager.Wages);<br>13         }<br>14 <br>15         static void Main(string[] args)<br>16         {<br>17             Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages);<br>18             Worker worker = new Worker();<br>19             worker.Wages = 3000;<br>20             workerHander(worker);<br>21 <br>22             Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages);<br>23             Manager manager = new Manager();<br>24             manager.Wages = 4500;<br>25             managerHandler(manager);<br>26 <br>27             Console.ReadKey();<br>28         }<br>29     }
复制代码
运行结果
6.jpeg


回到目录
四、深入解析事件
4.1 事件的由来
在介绍事件之前大家可以先看看下面的例子, PriceManager 负责对商品价格进行处理,当委托对象 GetPriceHandler 的返回值大于100元,按8.8折计算,低于100元按原价计算。
  1. 1     public delegate double PriceHandler();<br> 2 <br> 3     public class PriceManager<br> 4     {<br> 5         public PriceHandler GetPriceHandler;<br> 6 <br> 7         //委托处理,当价格高于100元按8.8折计算,其他按原价计算<br> 8         public double GetPrice()<br> 9         {<br>10             if (GetPriceHandler.GetInvocationList().Count() > 0)<br>11             {<br>12                 if (GetPriceHandler() > 100)<br>13                     return GetPriceHandler()*0.88;<br>14                 else<br>15                     return GetPriceHandler();<br>16             }<br>17             return -1;<br>18         }<br>19     }<br>20 <br>21     class Program<br>22     {<br>23         static void Main(string[] args)<br>24         {<br>25             PriceManager priceManager = new PriceManager();<br>26             <br>27             //调用priceManager的GetPrice方法获取价格<br>28             //直接调用委托的Invoke获取价格,两者进行比较<br>29             priceManager.GetPriceHandler = new PriceHandler(ComputerPrice);<br>30             Console.WriteLine(string.Format("GetPrice\n  Computer's price is {0}!",<br>31                 priceManager.GetPrice()));<br>32             Console.WriteLine(string.Format("Invoke\n  Computer's price is {0}!",<br>33                 priceManager.GetPriceHandler.Invoke()));<br>34             <br>35             Console.WriteLine();<br>36             <br>37             priceManager.GetPriceHandler = new PriceHandler(BookPrice);<br>38             Console.WriteLine(string.Format("GetPrice\n  Book's price is {0}!",<br>39                 priceManager.GetPrice()));<br>40             Console.WriteLine(string.Format("Invoke\n  Book's price is {0}!" ,<br>41                 priceManager.GetPriceHandler.Invoke()));<br>42             <br>43             Console.ReadKey();<br>44         }<br>45         //书本价格为98元<br>46         public static double BookPrice()<br>47         {<br>48             return 98.0;<br>49         }<br>50         //计算机价格为8800元<br>51         public static double ComputerPrice()<br>52         {<br>53             return 8800.0;<br>54         }<br>55     }
复制代码
运行结果
7.jpeg

观察运行的结果,如果把委托对象 GetPriceHandler 设置为 public ,外界可以直接调用 GetPriceHandler.Invoke 获取运行结果而移除了 GetPrice 方法的处理,这正是开发人员最不想看到的。
为了保证系统的封装性,开发往往需要把委托对象 GetPriceHandler 设置为 private, 再分别加入 AddHandler,RemoveHandler 方法对 GetPriceHandler 委托对象进行封装。
  1. 1     public delegate double PriceHandler();<br> 2 <br> 3     public class PriceManager<br> 4     {<br> 5         private PriceHandler GetPriceHandler;<br> 6 <br> 7         //委托处理,当价格高于100元按8.8折计算,其他按原价计算<br> 8         public double GetPrice()<br> 9         {<br>10             if (GetPriceHandler!=null)<br>11             {<br>12                 if (GetPriceHandler() > 100)<br>13                     return GetPriceHandler()*0.88;<br>14                 else<br>15                     return GetPriceHandler();<br>16             }<br>17             return -1;<br>18         }<br>19 <br>20         public void AddHandler(PriceHandler handler)<br>21         {<br>22             GetPriceHandler += handler;<br>23         }<br>24 <br>25         public void RemoveHandler(PriceHandler handler)<br>26         {<br>27             GetPriceHandler -= handler;<br>28         }<br>29     }<br>30     ................<br>31     ................
复制代码
为了保存封装性,很多操作都需要加入AddHandler、RemoveHandler 这些相似的方法代码,这未免令人感到厌烦。
为了进一步简化操作,事件这个概念应运而生。


4.2 事件的定义
事件(event)可被视作为一种特别的委托,它为委托对象隐式地建立起add_XXX、remove_XXX 两个方法,用作注册与注销事件的处理方法。而且事件对应的变量成员将会被视为 private 变量,外界无法超越事件所在对象直接访问它们,这使事件具备良好的封装性,而且免除了add_XXX、remove_XXX等繁琐的代码。
  1. 1     public class EventTest<br>2     {<br>3         public delegate void MyDelegate();<br>4         public event MyDelegate MyEvent;<br>5     }
复制代码
观察事件的编译过程可知,在编译的时候,系统为 MyEvent 事件自动建立add_MyEvent、remove_MyEvent 方法。
8.jpeg

 
4.3 事件的使用方式
事件能通过+=和-=两个方式注册或者注销对其处理的方法,使用+=与-=操作符的时候,系统会自动调用对应的 add_XXX、remove_XXX 进行处理。
值得留意,在PersonManager类的Execute方法中,如果 MyEvent 绑定的处理方法不为空,即可使用MyEvent(string)引发事件。但如果在外界的 main 方法中直接使用 personManager.MyEvent (string) 来引发事件,系统将引发错误报告。这正是因为事件具备了良好的封装性,使外界不能超越事件所在的对象访问其变量成员。
注意在事件所处的对象之外,事件只能出现在+=,-=的左方。
 此时,开发人员无须手动添加 add_XXX、remove_XXX 的方法,就可实现与4.1例子中的相同功能,实现了良好的封装。
  1. 1     public delegate void MyDelegate(string name);<br> 2 <br> 3     public class PersonManager<br> 4     {<br> 5         public event MyDelegate MyEvent;<br> 6 <br> 7         //执行事件<br> 8         public void Execute(string name)<br> 9         {<br>10             if (MyEvent != null)<br>11                 MyEvent(name);<br>12         }<br>13     }<br>14 <br>15     class Program<br>16     {<br>17         static void Main(string[] args)<br>18         {<br>19             PersonManager personManager = new PersonManager();<br>20             //绑定事件处理方法<br>21             personManager.MyEvent += new MyDelegate(GetName);<br>22             personManager.Execute("Leslie");<br>23             Console.ReadKey();<br>24         }<br>25 <br>26         public static void GetName(string name)<br>27         {<br>28             Console.WriteLine("My name is " + name);<br>29         }<br>30     }
复制代码
 
4.4 事件处理方法的绑定
在绑定事件处理方法的时候,事件出现在+=、-= 操作符的左边,对应的委托对象出现在+=、-= 操作符的右边。对应以上例子,事件提供了更简单的绑定方式,只需要在+=、-= 操作符的右方写上方法名称,系统就能自动辩认。
  1. 1     public delegate void MyDelegate(string name);<br> 2 <br> 3     public class PersonManager<br> 4     {<br> 5         public event MyDelegate MyEvent;<br> 6         .........<br> 7     }<br> 8 <br> 9     class Program<br>10     {<br>11         static void Main(string[] args)<br>12         {<br>13             PersonManager personManager = new PersonManager();<br>14             //绑定事件处理方法<br>15             personManager.MyEvent += GetName;<br>16             .............<br>17         }<br>18 <br>19         public static void GetName(string name)<br>20         {.........}<br>21    }
复制代码
如果觉得编写 GetName 方法过于麻烦,你还可以使用匿名方法绑定事件的处理。
  1. 1     public delegate void MyDelegate(string name);<br> 2 <br> 3     public class PersonManager<br> 4     {<br> 5         public event MyDelegate MyEvent;<br> 6 <br> 7         //执行事件<br> 8         public void Execute(string name)<br> 9         {<br>10             if (MyEvent != null)<br>11                 MyEvent(name);<br>12         }<br>13 <br>14         static void Main(string[] args)<br>15         {<br>16             PersonManager personManager = new PersonManager();<br>17             //使用匿名方法绑定事件的处理<br>18             personManager.MyEvent += delegate(string name){<br>19                 Console.WriteLine("My name is "+name);<br>20             };<br>21             personManager.Execute("Leslie");<br>22             Console.ReadKey();<br>23         }<br>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. 1 <html xmlns="http://www.w3.org/1999/xhtml"><br> 2 <head runat="server"><br> 3     <title></title><br> 4     <br>16 </head><br>17 <body><br>18     <form id="form1" runat="server"><br>19     <br>20        <br>21     <br>22     </form><br>23 </body><br>24 </html>
复制代码
更多时候,只需要在页面使用 OnClick=“btn_onclick" 方法,在编译的时候系统就会自动对事件处理方法进行绑定。
  1. 1 <html xmlns="http://www.w3.org/1999/xhtml"><br> 2 <head runat="server"><br> 3     <title></title><br> 4     <br>11 </head><br>12 <body><br>13     <form id="form1" runat="server"><br>14     <br>15        <br>16     <br>17     </form><br>18 </body><br>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. 1     public class MyEventArgs : EventArgs<br> 2     {<br> 3         private string args;<br> 4 <br> 5         public MyEventArgs(string message)<br> 6         {<br> 7             args = message;<br> 8         }<br> 9 <br>10         public string Message<br>11         {<br>12             get { return args; }<br>13             set { args = value; }<br>14         }<br>15     }<br>16 <br>17     public class EventManager<br>18     {<br>19         public event EventHandler<MyEventArgs> myEvent;<br>20 <br>21         public void Execute(string message)<br>22         {<br>23             if (myEvent != null)<br>24                 myEvent(this, new MyEventArgs(message));<br>25         }<br>26     }<br>27 <br>28     class Program<br>29     {<br>30         static void Main(string[] args)<br>31         {<br>32             EventManager eventManager = new EventManager();<br>33             eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage);<br>34             eventManager.Execute("How are you!");<br>35             Console.ReadKey();<br>36         }<br>37 <br>38         public static void ShowMessage(object obj,MyEventArgs e)<br>39         {<br>40             Console.WriteLine(e.Message);<br>41         }<br>42     }
复制代码
运行结果
9.jpeg

 
4.6 为用户控件建立事件
在ASP.NET开发中,页面往往会出现很多类似的控件与代码,开发人员可以通过用户控件来避免重复的代码。但往往同一个用户控件,在不同的页面中需要有不同的响应。此时为用户控件建立事件,便可轻松地解决此问题。
下面例子中,在用户控件 MyControl 中建立存在一个GridView控件,GridView 控件通过 GetPersonList 方法获取数据源。在用户控件中还定义了 RowCommand 事件,在 GridView 的 GridView_RowCommand 方法中激发此事件。这样,在页面使用此控件时,开发人员就可以定义不同的方法处理 RowCommand 事件。
  1. 1 public class Person<br> 2 {<br> 3     public int ID<br> 4     { get; set; }<br> 5     public string Name<br> 6     { get; set; }<br> 7     public int Age<br> 8     { get; set; }<br> 9 }<br>10 <br>11 <br>12 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %><br>13 <br>41 <br>42    43         onrowcommand="GridView1_RowCommand"><br>44       <Columns><br>45           <br>46           <br>47           <br>48           <br>49       </Columns><br>50    </asp:GridView><br>51 <br>52 <br>53 <br>54 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %><br>55 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %><br>56 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br>57 <br>58 <html xmlns="http://www.w3.org/1999/xhtml"><br>59 <head runat="server"><br>60     <title></title><br>61     <br>72 </head><br>73 <body><br>74     <form id="form1" runat="server"><br>75     <br>76        </ascx:myControl><br>77        <br /><br>78         Select Name : </asp:Label><br /><br>79     <br>80     </form><br>81 </body><br>82 </html>
复制代码
运行结果
10.jpeg

 
使用控件已有的事件固然简单,但它限制了传送的参数类型,使开发人员无法传送额外的自定义参数。在结构比较复杂的用户控件中,使用已有的控件事件,显然不够方便,此时,您可以考虑为用户控件建立自定义事件。
首先用户控件中包含订单信息与订单明细列表,首先定义一个事件参数 MyEventArgs,里面包含了订单信息与一个 OrderItem 数组。然后建立用户控件的委托MyDelegate 与对应的事件 MyEvent,在 Button 的 Click 事件中激发 MyEvent 自定义事件。这样在页面处理方法 myControl_Click 中就可以通过事件参数 MyEventArgs 获取用户控件中的属性,计算订单的总体价格。
  1.   1 <br>  2  public class OrderItem<br>  3  {<br>  4      public OrderItem(string id,string goods,double price,int count)<br>  5      {<br>  6          this.OrderItemID = id;     //明细单ID<br>  7          this.Goods = goods;        //商品名称<br>  8          this.Price = price;        //商品单价<br>  9          this.Count = count;        //商品数量 <br> 10      }<br> 11  <br> 12      public string OrderItemID<br> 13      { get; set; }<br> 14      public string Goods<br> 15      { get; set; }<br> 16      public double Price<br> 17      { get; set; }<br> 18      public int Count<br> 19      { get; set; }<br> 20  }<br> 21  <br> 22  /// 事件参数<br> 23  public class MyEventArgs:EventArgs<br> 24  {<br> 25      public MyEventArgs(string name,string address,string tel,<br> 26                         string orderCode,IList<OrderItem> orderItemList)<br> 27      {<br> 28          Name = name;    //买家姓名<br> 29          Address = address;    //买家地址<br> 30          Tel = tel;    //买家电话<br> 31          OrderCode = orderCode;     //订单号码<br> 32          OrderItemList = orderItemList;     //订单明细<br> 33      }<br> 34  <br> 35      public string Name<br> 36      { get;set; }<br> 37      public string Address<br> 38      { get; set; }<br> 39      public string Tel<br> 40      { get; set; }<br> 41      public string OrderCode<br> 42      { get; set; }<br> 43      public IList<OrderItem> OrderItemList<br> 44      { get; set; }<br> 45  }<br> 46  <br> 47  <br> 48  <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %><br> 49  <br> 82  <br> 83     Name : Leslie</asp:Label><br /><br> 84     Address : ZhongShan University 2A 501</asp:Label><br /><br> 85     Tel : 13660123456</asp:Label><br /><br> 86     Order Code : A12012031223B0030</asp:Label><br /><br /><br> 87     <br> 88        <Columns><br> 89            <br> 90            <br> 91            <br> 92            <br> 93        </Columns><br> 94     </asp:GridView><br> 95     <br /><br> 96     <br> 97  <br> 98  <br> 99  <br>100  <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %><br>101  <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %><br>102  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br>103  <br>104  <html xmlns="http://www.w3.org/1999/xhtml"><br>105  <head runat="server"><br>106      <title></title><br>107      <br>121  </head><br>122  <body><br>123      <form id="form1" runat="server"><br>124      <br>125         </ascx:myControl><br>126         <br /><br>127          OrderCode : </asp:Label><br /><br>128          TotalPrice :  </asp:Label><br>129      <br>130      </form><br>131  </body><br>132  </html>
复制代码
运行结果
11.jpeg


若对自定义事件不太熟悉的朋友很多时候会使用 UserControl.FindControl 的方式获取用户控件中的属性,但当你深入了解自定义事件的开发过程以后,就能有效简化开发的过程。

回到目录
五、Lambda 表达式
5.1 Lambda 的意义
在Framework 2.0 以前,声明委托的唯一方法是通过方法命名,从Framework 2.0 起,系统开始支持匿名方法。
通过匿名方法,可以直接把一段代码绑定给事件,因此减少了实例化委托所需的编码系统开销。
而在 Framework 3.0 开始,Lambda 表达式开始逐渐取代了匿名方法,作为编写内联代码的首选方式。总体来说,Lambda 表达式的作用是为了使用更简单的方式来编写匿名方法,彻底简化委托的使用方式。
 
5.2 回顾匿名方法的使用
匿名方法的使用已经在4.4节简单介绍过,在此回顾一下。
使用下面的方式,可以通过匿名方法为Button的Click事件绑定处理方法。
  1. 1         static void Main(string[] args)<br>2         {<br>3             Button btn = new Button();<br>4             btn.Click+=delegate(object obj,EventArgs e){<br>5                 MessageBox.Show("Hello World !");<br>6             };<br>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. 1     class Program<br> 2     {<br> 3         static void Main(string[] args)<br> 4         {<br> 5             List<Person> list = GetList();<br> 6             //绑定查询条件<br> 7             Predicate<Person> predicate = new Predicate<Person>(Match);<br> 8             List<Person> result = list.FindAll(predicate);<br> 9             Console.WriteLine(“Person count is : ” + result.Count);<br>10             Console.ReadKey();<br>11         }<br>12         //模拟源数据<br>13         static List<Person> GetList()<br>14         {<br>15             var personList = new List<Person>();<br>16             var person1 = new Person(1,"Leslie",29);<br>17             personList.Add(person1);<br>18             ........<br>19             return personList;<br>20         }<br>21         //查询条件<br>22         static bool Match(Person person)<br>23         {<br>24             return person.Age <= 30;<br>25         }<br>26     }<br>27 <br>28     public class Person<br>29     {<br>30         public Person(int id, string name, int age)<br>31         {<br>32             ID = id;<br>33             Name = name;<br>34             Age = age;<br>35         }<br>36 <br>37         public int ID<br>38         { get; set; }<br>39         public string Name<br>40         { get; set; }<br>41         public int Age<br>42         { get; set; }<br>43     }
复制代码
例子二,通过 Action 把表达式定义 x=x+500, 到最后输入参数1000,得到的结果与例子一相同。
注意,此处Lambda表达式定义的操作使用 { } 括弧包括在一起,里面可以包含一系列的操作。
  1. 1         static void Main(string[] args)<br> 2         {<br> 3             Action<string> action=ShowMessage;<br> 4             action("Hello World");<br> 5             Console.ReadKey();<br> 6         }<br> 7 <br> 8         static void ShowMessage(string message)<br> 9         {<br>10             MessageBox.Show(message);<br>11         }
复制代码
 
例子三,定义一个Predicate,当输入值大约等于1000则返回 true , 否则返回 false。与5.3.1的例子相比,Predicate的绑定不需要显式建立一个方法,而是直接在Lambda表达式里完成,简洁方便了不少。
  1. 1         static void Main(string[] args)<br> 2         {<br> 3             Func<double, bool, double> func = Account;<br> 4             double result=func(1000, true);<br> 5             Console.WriteLine("Result is : "+result);<br> 6             Console.ReadKey();<br> 7         }<br> 8 <br> 9         static double Account(double a,bool condition)<br>10         {<br>11             if (condition)<br>12                 return a * 1.5;<br>13             else<br>14                 return a * 2;<br>15         }
复制代码
 
例子四,在计算商品的价格时,当商品重量超过30kg则打9折,其他按原价处理。此时可以使用Func,参数1为商品原价,参数2为商品重量,最后返回值为 double 类型。
  1. 1         static void Main(string[] args)<br>2         {<br>3             int x = 1000;<br>4             Action action = () => x = x + 500;<br>5             action.Invoke();<br>6 <br>7             Console.WriteLine("Result is : " + x);<br>8             Console.ReadKey();<br>9         }
复制代码
例子五,使用Lambda为Button定义Click事件的处理方法。与5.2的例子相比,使用Lambda比使用匿名方法更加简单。
  1. 1         static void Main(string[] args)<br> 2         {<br> 3             Action<int> action = (x) =><br> 4             {<br> 5                 x = x + 500;<br> 6                 Console.WriteLine("Result is : " + x);<br> 7             };<br> 8             action.Invoke(1000);<br> 9             Console.ReadKey();<br>10         }
复制代码
例子六,此处使用5.3.1的例子,在List的FindAll方法中直接使用Lambda表达式。
相比之下,使用Lambda表达式,不需要定义Predicate对象,也不需要显式设定绑定方法,简化了不工序。
  1. 1         static void Main(string[] args)<br> 2         {<br> 3             Predicate<int> predicate = (x) =><br> 4             {<br> 5                 if (x >= 1000)<br> 6                     return true;<br> 7                 else<br> 8                     return false;<br> 9             };<br>10             bool result=predicate.Invoke(500);<br>11             Console.ReadKey();<br>12         }
复制代码
当在使用LINQ技术的时候,到处都会弥漫着 Lambda 的身影,此时更能体现 Lambda 的长处。
但 LINQ 涉及到分部类,分部方法,IEnumerable,迭代器等多方面的知识,这些已经超出本章的介绍范围。
通过这一节的介绍,希望能够帮助大家更深入地了解 Lambda 的使用。
 回到目录
本章小结
本章主要介绍了委托(Delegate)的使用,委托对象是一个派生自 System.MultcastDelegate 的类,它能通过 Invoke 方式进行同步调用,也可以通过 BeginInvoke,EndInvoke 方式实现异步调用。而事件(Event)属于一种特殊的委托,它与委托类型同步使用,可以简化的开发过程。
最后,本文还介绍了匿名方法的使用方式,以及 Lambda 表达式的由来。
对 .NET 开发有兴趣的朋友欢迎加入QQ群:162338858 共同探讨 !
 
C#综合揭秘
通过修改注册表建立Windows自定义协议
Entity Framework 并发处理详解
细说进程、应用程序域与上下文  
细说多线程(上)
细说多线程(下)
细说事务
深入分析委托与事件
 
作者:风尘浪子
http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html
原创作品,转载时请注明作者及出处






来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册