http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html
引言
本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单。
还将为您解释委托的协变与逆变,以及如何使用 Delegate 使 Observer(观察者)模式的使用变得更加简单。
在事件的介绍上,会讲述事件的使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用。
最后一节,将介绍Predicate<T>、Action<T>、Func<T,TResult>多种泛型委托的使用和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 被视为完全无关的两个类型。