(zz)从callback的角度来理解.NET/C# 中的 委托 (delegate)与 事件 (event)
delegate是个比较生僻的单词(其实是我英语差~)。那先看看简要的来自金山词霸的解释:
*1.代表, 代表团成员
He is one of the delegates to the conference.
他是与会代表之一。
及物动词 vt. 其他读音:[ˈdeliɡeit]
*1.任命或委派…为代表
He delegated me to perform a task.
他委派我去执行一项任务。
*2.托付
*3. 授(权);把(工作、权力等)委托(给下级);选派(某人做某事)
delegate
n
a person chosen or elected to act for or represent another or others, esp at a conference or meeting
a representative of a territory in the US House of Representatives
vb
to give or commit (duties, powers, etc) to another as agent or representative; depute
to send, authorize, or elect (a person) as agent or representative
to assign (a person owing a debt to oneself) to one's creditor in substitution for oneself
就跟有一种类叫String一样,有一种类叫Delegate,String类的对象一般都是字符序列,而Delegate类的对象一般是某种类型的函数,只要一个函数的类型,包括参数定义跟返回值类型,跟声明委托时的定义是一致的,我们就可以把这个函数名传递给某个这种委托类的实例,通过这个委托类的实例来操纵这个函数。也就是说实现了根据类型来按需调用满足条件的一个或一系列同类型的函数。
设计这么一个委托语法,倒底给程序设计带来了哪些方便呢???
委托的语法很容易理解,即使是委托链,+=,-=,泛型委托,都不难懂,但是要把委托用起来,不容易,因为我很不明白委托这玩意儿存在的意义,如果是要调用一个函数,为什么不直接调用呢,绕一个“中介”的弯子倒底意义何在。 看书(C#与.NET3.5高级程序设计)没明白,又网上找了一堆文章来看,大致了解到的相关词汇有:观察者模式,回调。。。
我发现回调对理解委托是很重要的,回调这个概念只是一个翻译词汇,其本文是 callback,
callback, 它应该被理解为 call me back .
Suppose I need you to do something for me, I have two options:
1. Hand you the work and keep waiting, until you get the work done;
2. Hand you the work, then, continue doing my other things, when you are finished,
CALL ME BACK over the phone, let me know the result.
不是经常会在电影里碰到这种情节么,男猪脚1在讲电话,男猪脚2进门了,男猪脚1对电话说:I gotta go, I'll call you back.(以上两段来自亲爱的高手ZG)
也就是说A调用了函数B,这只是call,不是callback。而是你先交待我call你,然后我call了你,这才叫callback!!!!!
好了,明白了callback,现在自然的思路就是:我是A, B委托让我在我这边的某事件发生后callback他,好的,到时个我A这边事件发生了,我就直接调用B指定的那个函数,callback完毕!要注意这个事件发生的次序,B委托A,然后A中的事件发生,然后A调用了B的函数。有三个部分组成!
但是这种设计方式是不好的!!!!!!问题出在A直接call了委托者。这时假如要增加一个需要通知的对象,那就要改变A对象的内容,再增加一个,再改变。同样,当一个对象需要减除接受这个通知时,又要改变A对象的内容,要在A对象中维护这个需求,实在是不爽,因为对A来说,跟其它对象的耦合度太高了。那怎么办呢?
引入一个第三方对象X,它维护一个需要通知的列表,任何对象需要得到通知的时候,就告诉X而不是去直接委托A。而做为A,本身不需要关心要通知到哪些对象,只需要告诉X,事件发生了。该通知哪些对象是你X的事。但你会问,那这样不是把维护通知者列表的复杂性转移到C对象上去了么?并且还好端端多出一个对象来,岂不是把事情搞得更复杂了?并不是的,这儿其实就相当于把一个复杂易变的功能从A中剥离出来,A只负责做好他的更重要的工作,关注于事件本身,而不需要关注事件发生了,要通知哪些对象。这样,确实是降低了整个系统的耦合度。并且有时候会有一种情况:想接受到通知的某个对象,并不想让A知道他侦听了这个事件!!!这时候,更是需要第三方对象X的存在。
这时候我就大致明白 callback 以及如何实现一个设计良好的 callback 了,然后,callback与委托又有什么关系呢?委托就是用来方便地实现callback的手段!!!
这时我们考虑这样一个模拟情景,涉及到两个对象,一个员工,一个Leader在做一个项目,项目一开始时Leader就告诉员工,每当员工的完成一个TASK的时候,就要向LEADER报告,由LEADER来REVIEW并更新WBS。这是一个典型的callback事件。根据我们前面的考量,我们知道,不能由员工直接通知Leader,这是不好的设计,比如说:某天忽然Manager心血来潮,想密切关注该项目的进度,但他又不想让员工知道这个,怕给员工增加压力。(真是好领导呀~~~)。所以,我们买了一个机器人,当员工每完成一个TASK,员工就踢机器人一脚,机器人就会跑去通知Leader。当经理需要关注进度时,就给机器人下指令,员工踢你时你也要告诉我。这样,问题就都解决了。设计如下:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace DelegateTest1
7 {
8 class Employee
9 {
10 publicvoid KickRobot(Robot r)
11 {
12 Console.WriteLine("I have finished a task!");
13 r.NoticeOthers();
14 }
15 }
16
17 class Leader
18 {
19 publicvoid AcceptNotice()
20 {
21 Console.WriteLine("Yes,i will check it!");
22 }
23 }
24
25 class Manager
26 {
27 publicvoid AcceptNotice()
28 {
29 Console.WriteLine("UMM,good boy!");
30 }
31 }
32
33 class Robot
34 {
35 private List<object> NoticeList=new List<object>();
36 publicvoid NoticeOthers()
37 {
38 foreach (object s in NoticeList)
39 {
40 if (s is Leader)
41 {
42 ((Leader)s).AcceptNotice();
43 }
44 if (s is Manager)
45 {
46 ((Manager)s).AcceptNotice();
47 }
48 }
49 }
50 publicvoid AddToList(object a)
51 {
52 NoticeList.Add(a);
53 }
54 }
55
56 class Program
57 {
58 staticvoid Main(string[] args)
59 {
60 Employee XiaoLi =new Employee();
61 Leader LaoLiu =new Leader();
62 Manager AnZong =new Manager();
63 Robot WillSmith =new Robot();
64
65 WillSmith.AddToList(LaoLiu);//Only leader LaoLiu want to know that.
66 XiaoLi.KickRobot(WillSmith);
67
68 Console.WriteLine("");
69
70 WillSmith.AddToList(AnZong);//The Manger suddenly want to know that too.
71 XiaoLi.KickRobot(WillSmith);
72 }
73 }
74 }
仔细考虑上面的代码,我们“买”了一个Robot,花费了额外的“代价”,并且,这一流程有点绕弯子,是否可以直接在员工类中开放一个接口,需要监听者直接往这个接口里面放函数,到时我做了就OK。这时候,委托就派上用场了,如下::::
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace DelegateTest2
7 {
8 class Employee
9 {
10 publicdelegatevoid NoticeTarget(string msg);
11 private NoticeTarget NoticeList;
12 publicvoid AddToNoticeList(NoticeTarget ExternalMethod)
13 {
14 NoticeList += ExternalMethod;
15 }
16 publicvoid Notice()
17 {
18 if (NoticeList !=null)
19 {
20 NoticeList("I have finished a task!");
21 }
22 }
23 }
24
25 class Leader
26 {
27 publicvoid AcceptNotice(string msg)
28 {
29 Console.WriteLine(msg+" Yes,i will check it!");
30 }
31 }
32
33 class Manager
34 {
35 publicvoid AcceptNotice(string msg)
36 {
37 Console.WriteLine(msg+" UMM,good boy!");
38 }
39 }
40
41 class Program
42 {
43 staticvoid Main(string[] args)
44 {
45 Employee XiaoLi =new Employee();
46 Leader LaoLiu =new Leader();
47 Manager AnZong =new Manager();
48
49 XiaoLi.AddToNoticeList(LaoLiu.AcceptNotice);
50 XiaoLi.AddToNoticeList(AnZong.AcceptNotice);
51
52 XiaoLi.Notice();
53 }
54 }
55 }
看看。。。。。。是不是比较简洁了呀,Employee类可以保持不变,只需要在外面尽管加监听者就够了,它并不需要关心谁监听了它,反正,它只管发通知。并且,这个通知发出去,至于监听者需要怎么处理,比如如上的那个msg,怎么处理,是监听者的事。并且,如果需要,可以方便的定义撤销监听的方法。只需要-=就可以了。
继续观察前面的代码,,,我们就会发现,要实现一个合适的委托系统,需要做四步,1:定义委托类型,2:定义一个委托类型的私有变量,3:定义一个操作该私有变量的外部方法,4:触发委托,发出通知! 这个结构在每一个委托中,差不多都是重复要做的,于是,进一步抽象,我们发明了event来进一步简化程序设计。如下:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace DelegateTest2
7 {
8 class Employee
9 {
10 publicdelegatevoid EmpEventHandler(string msg);
11 publicevent EmpEventHandler TaskFinished;
12
13 publicvoid Notice()
14 {
15 if (TaskFinished !=null)
16 {
17 TaskFinished("I have finished a task!");
18 }
19 }
20 }
21
22 class Leader
23 {
24 publicvoid AcceptNotice(string msg)
25 {
26 Console.WriteLine(msg+" Yes,i will check it!");
27 }
28 }
29
30 class Manager
31 {
32 publicvoid AcceptNotice(string msg)
33 {
34 Console.WriteLine(msg+" UMM,good boy!");
35 }
36 }
37
38 class Program
39 {
40 staticvoid Main(string[] args)
41 {
42 Employee XiaoLi =new Employee();
43 Leader LaoLiu =new Leader();
44 Manager AnZong =new Manager();
45
46 XiaoLi.TaskFinished+=LaoLiu.AcceptNotice;
47 XiaoLi.TaskFinished += AnZong.AcceptNotice;
48
49 XiaoLi.Notice();
50 }
51 }
52 }
使用了事件,如上的1,2,4都没省,其实只是省了个3,也就是添加,移除事件监听者的那两个方法,只是个C#的语法糖。
在这一机制中,微软有推荐一个事件机制。查看基类库中某个类型发送的事件时,会发现底层委托的第一个参数是一个System.Object,第二个参数是一个派生自System.Args的类型。第一个参数表示一个对发送事件对象的引用,第二个参数则表示与该事件相关的信息。
也就是说,我们的标准的事件处理函数应该是这个样子:
public void AcceptNotice(object sender,EventArgs e)
{
Console.WriteLine(e.msg+" Yes,i will check it!");
}
看到了没,跟ASP.NET的 PageLoad()函数及其它的事件处理函数是不是很相似呀。。。。。
语法优化的脚步不能停止。就关于委托与事件,继续往前走。当一个调用者想监听传进来的事件时,它必须定义一个唯一的与相关联委托签名相匹配的方法。比如上面的AcceptNotice()方法,思考一下会发现,这样的方法除了被调用委托之外,很少被其它的部分所调用。从生产效率的角度来说,手工定义一个由委托对象调用的方法显得有些繁琐,为了解决这一种情况,现在可以在事件注册时直接将一个委托与一段代码相关联。这种代码的正式名称就是 “匿名方法” !如下所示:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace DelegateTest2
7 {
8 class Employee
9 {
10 publicdelegatevoid EmpEventHandler(string msg);
11 publicevent EmpEventHandler TaskFinished;
12
13 publicvoid Notice()
14 {
15 if (TaskFinished !=null)
16 {
17 TaskFinished("I have finished a task!");
18 }
19 }
20 }
21
22 class Leader
23 {
24 }
25
26 class Manager
27 {
28 }
29
30 class Program
31 {
32 staticvoid Main(string[] args)
33 {
34 Employee XiaoLi =new Employee();
35 Leader LaoLiu =new Leader();
36 Manager AnZong =new Manager();
37
38 XiaoLi.TaskFinished +=delegate(string msg)
39 {
40 Console.WriteLine(msg +" Yes,i will check it!");
41 };
42 XiaoLi.TaskFinished +=delegate(string msg)
43 {
44 Console.WriteLine(msg +" UMM,good boy!");
45 };
46
47 XiaoLi.Notice();
48 }
49 }
50 }
匿名方法很有趣,它使我们能访问定义它们的方法的局部变量。这些变量称为匿名方法的“外部变量”。比如前面的代码,可以在Main()方法中定义一个计数器,局部变量 int ReporTimes; 然后在匿名方法中使用这个变量,来统计这个Notice倒底发生了多少次。
有了匿名方法后,人类真是懒啊。。。甚至连delegate,或者参数列表的两个括号,都不愿意重复写了。。。引入了Lambda表达式,前面的代码可以进一步简化如下:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace DelegateTest2
7 {
8 class Employee
9 {
10 publicdelegatevoid NoticeList(string msg);
11 private NoticeList MyNoticeList;
12
13 publicvoid AddToNoticeList(NoticeList np)
14 {
15 MyNoticeList += np;
16 }
17
18 publicvoid Notice()
19 {
20 if (MyNoticeList !=null)
21 {
22 MyNoticeList("I have finished a task!");
23 }
24 }
25 }
26
27 class Leader
28 {
29 }
30
31 class Manager
32 {
33 }
34
35 class Program
36 {
37 staticvoid Main(string[] args)
38 {
39 Employee XiaoLi =new Employee();
40 Leader LaoLiu =new Leader();
41 Manager AnZong =new Manager();
42
43 XiaoLi.AddToNoticeList(msg => { Console.WriteLine(msg +" Yes,i will check it!"); });
44 XiaoLi.AddToNoticeList(msg => { Console.WriteLine(msg +" UMM,good boy!"); });
45
46 XiaoLi.Notice();
47 }
48 }
49 }
注意这次我们没有用event.而是把匿名方法用Lambda表达式的方式加在了原始的私有委托列表中。
另外,Lambda表达式在LINQ中也有大量的应用!
从1.0的没有泛型,到3.5的Lambda表达式,LINQ,.NET的语法功能及编程效率在步步提高。并且也增加了学习难度,用熟了,很简洁,没上手时,往着一看,水深得吓人!