• 深入分析委托与事件


    引言
    
    
    本篇文章将为你介绍一下 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
      Computer's price is {0}!",
    31                 priceManager.GetPrice()));
    32             Console.WriteLine(string.Format("Invoke
      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
      Book's price is {0}!",
    39                 priceManager.GetPrice()));
    40             Console.WriteLine(string.Format("Invoke
      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 的使用。
  • 相关阅读:
    【ThreadX】Azure RTOS ThreadX概述
    -- spi flash 擦除接口调用HAL库不同函数的区别
    STM32 芯片锁死解决方法
    【KEIL】User's Guide
    【KEIL】Software Packs
    【KEIL 】Options for File
    使用CubeMX创建TouchGFX工程时LCD死活不显示
    【转】获取本地图片的URL
    printf 函数格式控制
    【集成】touchgfx 之 《Using C code with TouchGFX》
  • 原文地址:https://www.cnblogs.com/profession/p/8968263.html
Copyright © 2020-2023  润新知