• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C#综合揭秘——深入分析委托与事件 多种泛型委托的使用和Lambda的发展过程与其使用方 ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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

引言

本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单。 还将为您解释委托的协变与逆变,以及如何使用 Delegate 使 Observer(观察者)模式的使用变得更加简单。 在事件的介绍上,会讲述事件的使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用。 最后一节,将介绍Predicate<T>、Action<T>、Func<T,TResult>多种泛型委托的使用和Lambda的发展过程与其使用方式。 因为时间仓促,文中有错误的地方敬请点评。

 

 

目录

一、委托类型的来由

二、建立委托类

三、委托使用方式

四、深入解析事件

五、Lambda 表达式

 

 

 

一、委托类型的来由

记得在使用C语言的年代,整个项目中都充满着针指的身影,那时候流行使用函数指针来创建回调函数,使用回调可以把函数回调给程序中的另一个函数。但函数指针只是简单地把地址指向另一个函数,并不能传递其他额外信息。 在.NET中,在大部分时间里都没有指针的身影,因为指针被封闭在内部函数当中。可是回调函数却依然存在,它是以委托的方式来完成的。委托可以被视为一个更高级的指针,它不仅仅能把地址指向另一个函数,而且还能传递参数,返回值等多个信息。系统还为委托对象自动生成了同步、异步的调用方式,开发人员使用 BeginInvoke、EndInvoke 方法就可以抛开 Thread 而直接使用多线程调用 。

回到目录

 

二、建立委托类

使用delegate就可以直接建立任何名称的委托类型,当进行系统编译时,系统就会自动生成此类型。您可以使用delegate void MyDelegate() 方式建立一个委托类,并使用ILDASM.exe观察其成员。由ILDASM.exe 中可以看到,它继承了System.MulticastDelegate类,并自动生成BeginInvoke、EndInvoke、Invoke 等三个常用方法。

Invoke 方法是用于同步调用委托对象的对应方法,而BeginInvoke、EndInvoke是用于以异步方式调用对应方法的。 对于异步调用的使用方式,可以参考:C#综合揭秘——细说多线程

1      public class MyDelegate:MulticastDelegate 2      { 3          //同步调用委托方法4          public virtual void Invoke(); 5          //异步调用委托方法6          public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state); 7          public virtual void EndInvoke(IAsyncResult result); 8      }

MulticastDelegate是System.Delegate的子类,它是一个特殊类,编译器和其他工具可以从此类派生,但是自定义类不能显式地从此类进行派生。它支持多路广播委托,并拥有一个带有链接的委托列表,在调用多路广播委托时,系统将按照调用列表中的委托出现顺序来同步调用这些委托。

MulticastDelegate具有两个常用属性:Method、Target。其中Method 用于获取委托所表示的方法Target 用于获取当前调用的类实例。

MulticastDelegate有以下几个常用方法:

方法名称 说明
Clone   创建委托的浅表副本。
GetInvocationList   按照调用顺序返回此多路广播委托的调用列表。
GetMethodImpl 返回由当前的 MulticastDelegate 表示的静态方法。
GetObjectData   用序列化该实例所需的所有数据填充 SerializationInfo 对象。
MemberwiseClone   创建当前 Object 的浅表副本。
RemoveImpl   调用列表中移除与指定委托相等的元素

MulticastDelegate与Delegate给委托对象建立了强大的支持,下面向各位详细介绍一下委托的使用方式。

回到目录

 

三、委托使用方式

3.1 简单的委托

当建立委托对象时,委托的参数类型必须与委托方法相对应。只要向建立委托对象的构造函数中输入方法名称example.Method,委托就会直接绑定此方法。使用myDelegate.Invoke(string message),就能显式调用委托方法。但在实际的操作中,我们无须用到 Invoke 方法,而只要直接使用myDelegate(string message),就能调用委托方法。

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

 

3.2 带返回值的委托

当建立委托对象时,委托的返回值必须与委托方法相对应。使用下面的例子,方法将返回 “Hello Leslie” 。

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

 

3.3 多路广播委托

在第二节前曾经提过,委托类继承于MulticastDelegate,这使委托对象支持多路广播,即委托对象可以绑定多个方法。当输入参数后,每个方法会按顺序进行迭代处理,并返回最后一个方法的计算结果。 下面的例子中,Price 类中有两个计算方法,Ordinary 按普通的9.5折计算,Favourable 按优惠价 8.5 折计算。委托同时绑定了这两个方法,在输入参数100以后,Ordinary、Favourable这两个方法将按顺序迭代执行下去,最后返回 Favourable 方法的计算结果 85。

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

运行结果

3.4 浅谈Observer模式

回顾一下简单的 Observer 模式,它使用一对多的方式,可以让多个观察者同时关注同一个事物,并作出不同的响应。 例如下面的例子,Manager的底薪为基本工资的1.5倍,Assistant的底薪为基本工资的1.2倍。WageManager类的RegisterWorker方法与RemoveWorker方法可以用于注册和注销观察者,最后执行Execute方法可以对多个已注册的观察者同时输入参数。

 

 

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

运行结果

 

开发 Observer 模式时借助委托,可以进一步简化开发的过程。由于委托对象支持多路广播,所以可以把Worker类省略。在WageManager类中建立了一个委托对象wageHandler,通过Attach与Detach方法可以分别加入或取消委托。如果观察者想对事物进行监测,只需要加入一个委托对象即可。记得在第二节曾经提过,委托的GetInvodationList方法能获取多路广播委托列表,在Execute方法中,就是通过去多路广播委托列表去判断所绑定的委托数量是否为0。

 1         public delegate double Handler(double basicWages);  2    3          public class Manager  4          {  5              public double GetWages(double basicWages)  6              {  7                  double totalWages=1.5 * basicWages;  8                  Console.WriteLine("Manager's wages is : " + totalWages);  9                  return totalWages; 10              } 11          } 12   13          public class Assistant 14          { 15              public double GetWages(double basicWages) 16              { 17                  double totalWages = 1.2 * basicWages; 18                  Console.WriteLine("Assistant's wages is : " + totalWages); 19                  return totalWages; 20              } 21          } 22   23          public class WageManager 24          { 25              private Handler wageHandler; 26   27              //加入观察者28              public void Attach(Handler wageHandler1) 29              { 30                  wageHandler += wageHandler1; 31              } 32   33              //删除观察者34              public void Detach(Handler wageHandler1) 35              { 36                  wageHandler -= wageHandler1; 37              } 38   39              //通过GetInvodationList方法获取多路广播委托列表,如果观察者数量大于0即执行方法40              public void Execute(double basicWages) 41              { 42                  if (wageHandler!=null) 43                     if(wageHandler.GetInvocationList().Count() != 0) 44                         wageHandler(basicWages); 45              } 46   47              static void Main(string[] args) 48              { 49                  WageManager wageManager = new WageManager(); 50                  //加入Manager观察者51                  Manager manager = new Manager(); 52                  Handler managerHandler = new Handler(manager.GetWages); 53                  wageManager.Attach(managerHandler); 54   55                  //加入Assistant观察者56                  Assistant assistant = new Assistant(); 57                  Handler assistantHandler = new Handler(assistant.GetWages); 58                  wageManager.Attach(assistantHandler); 59   60                  //同时加入底薪3000元,分别进行计算61                  wageManager.Execute(3000); 62                  Console.ReadKey(); 63              } 64          }

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

 

3.5 委托的协变与逆变

在 Framework 2.0 出现之前,委托协变这个概念还没有出现。此时因为委托是安全类型,它们不遵守继承的基础规则。即会这下面的情况:Manager 虽然是 Worker 的子类,但 GetWorkerHander 委托不能直接绑定 GetManager 方法,因为在委托当中它们的返回值 Manager 与 Worker 被视为完全无关的两个类型。

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

自从Framework 2.0 面试以后,委托协变的概念就应运而生,此时委托可以按照传统的继承规则进行转换。即 GetWorkerHandler 委托可以直接绑定 GetManager 方法。

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

委托逆变,是指委托方法的参数同样可以接收 “继承” 这个传统规则。像下面的例子,以 object 为参数的委托,可以接受任何 object 子类的对象作为参数。最后可以在处理方法中使用 is 对输入数据的类型进行判断,分别处理对不同的类型的对象。

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

运行结果

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

3.6 泛型委托

委托逆变虽然实用,但如果都以 object 作为参数,则需要每次都对参数进行类型的判断,这不禁令人感到厌烦。 为此,泛型委托应运而生,泛型委托有着委托逆变的优点,同时利用泛型的特性,可以使一个委托绑定多个不同类型参数的方法,而且在方法中不需要使用 is 进行类型判断,从而简化了代码。

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

运行结果

回到目录

四、深入解析事件

4.1 事件的由来

在介绍事件之前大家可以先看看下面的例子, PriceManager 负责对商品价格进行处理,当委托对象 GetPriceHandler 的返回值大于100元,按8.8折计算,低于100元按原价计算。

 1     public delegate double PriceHandler();  2   3     public class PriceManager  4     {  5         public PriceHandler GetPriceHandler;  6   7         //委托处理,当价格高于100元按8.8折计算,其他按原价计算 8         public double GetPrice()  9         { 10             if (GetPriceHandler.GetInvocationList().Count() > 0) 11             { 12                 if (GetPriceHandler() > 100) 13                     return GetPriceHandler()*0.88; 14                 else 15                     return GetPriceHandler(); 16             } 17             return -1; 18         } 19     } 20  21     class Program 22     { 23         static void Main(string[] args) 24         { 25             PriceManager priceManager = new PriceManager(); 26              27             //调用priceManager的GetPrice方法获取价格 28             //直接调用委托的Invoke获取价格,两者进行比较29             priceManager.GetPriceHandler = new PriceHandler(ComputerPrice); 30             Console.WriteLine(string.Format("GetPrice\n  Computer's price is {0}!", 31                 priceManager.GetPrice())); 32             Console.WriteLine(string.Format("Invoke\n  Computer's price is {0}!", 33                 priceManager.GetPriceHandler.Invoke())); 34              35             Console.WriteLine(); 36              37             priceManager.GetPriceHandler = new PriceHandler(BookPrice); 38             Console.WriteLine(string.Format("GetPrice\n  Book's price is {0}!", 39                 priceManager.GetPrice())); 40             Console.WriteLine(string.Format("Invoke\n  Book's price is {0}!" , 41                 priceManager.GetPriceHandler.Invoke())); 42              43             Console.ReadKey(); 44         } 45         //书本价格为98元46         public static double BookPrice() 47         { 48             return 98.0; 49         } 50         //计算机价格为8800元51         public static double ComputerPrice() 52         { 53             return 8800.0; 54         } 55     }

运行结果

观察运行的结果,如果把委托对象 GetPriceHandler 设置为 public ,外界可以直接调用 GetPriceHandler.Invoke 获取运行结果而移除


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
【C#】Linq to Object发布时间:2022-07-10
下一篇:
C#EFAttach与Entry发布时间:2022-07-10
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap