上次我们简要且肤浅地介绍了委托,我们复习一下吧:委托是方法的类型,委托是和类同级的概念,使用delegate关键字声明,每一个委托都代表了具有相同参数和返回值的一类方法,委托并不是为了代表某一个方法而存在的,它并不关心其实例的具体实现和造成结果、访问修饰等等。
我们现在来略微深入地学习一下委托:我们主要介绍委托的可以代表多个同类方法的特性。
比如以下示例:
1 using System;
2
3 namespace DelegateEG
4 {
5
6 // 委托
7 public delegate void ExampleHandler();
8
9 class Program
10 {
11 // 静态成员
12 static ExampleHandler example;
13
14 static void Main(string[] args)
15 {
16 // 引用也是一个类型,为增强代码的刻度性,最好使用写法1
17 // 写法1:
18 example = new ExampleHandler(ShowMsg1);
19
20 // 写法2:
21 // example = ShowMsg1;
22
23 // 写法3,不写=或者使用构造方法
24 // example += new ExampleHandler(ShowMsg1);
25 // 或者
26 // example += ShowMsg1;
27
28 // 在为委托绑定方法
29 example += ShowMsg2;
30
31 // 调用委托
32 example();
33 Console.ReadKey();
34
35 // 输出:
36 /*这是测试方法1
37 *这是测试方法2
38 */
39 }
40
41 // 测试方法1
42 static void ShowMsg1()
43 {
44 Console.WriteLine("这是测试方法1");
45 }
46
47 // 测试方法2
48 static void ShowMsg2()
49 {
50 Console.WriteLine("这是测试方法2");
51 }
52 }
53 }
这里有一点需要注意:委托的方法调用是一次调用所有的已绑定方法,而且是同时的。这个可能在事件机制处理上我会举例详细解释。
我再最后一次梳理一下委托的概念:委托是一种类型,区分类类型、枚举类型等。委托是方法的类型,就如同类是对象的类型一样。每一种委托都代表了具有某种特定参数类型和数量,具有某种特定返回值的方法。该中委托的实例可以绑定一个或多个方法,方法必须是该委托能代表的,方法的参数和返回值必须受委托的完整约束。委托实例被调用执行时会调用它所绑定的所有方法。
那么基于已有的关于委托的知识,我们来学习一下事件。
关于事件的定义,我们先不说,时机到了再说。
我们先说事件为什么存在,它的理由是什么?
记住:事件是为了封装委托而存在的!
借助下面的几个例子,可能你对事件的理解会更加深入,至少不会停留在只会双击写代码的地步。
还记得我们我们在学生打招呼中怎么封装委托的吗?(我道歉一下,当时没有考虑,可能初学者不容易懂)
1 // 委托成员,我们用它来保存具体的打招呼的方法
2 private SayHiHandler _sayHi;
3 // 封装委托,让外界通过SayHi访问_syHi
4 // 由于委托是方法的类型,所以我们可以将它当方法用
5 // 在外界访问它之前将它绑定到具体方法
6 public SayHiHandler SayHi
7 {
8 get
9 {
10 switch (Language)
11 {
12 case LanguageType.Chinese:
13 _sayHi = ChineseSayHi;
14 break;
15 case LanguageType.English:
16 _sayHi = new SayHiHandler(EnglishSayHi);
17 break;
18 // ……
19
20 default:
21 break;
22 }
23 return _sayHi;
24 }
25 }
那么我们是怎么调用的呢?
1 public class Program
2 {
3 public static void Main(string[] args)
4 {
5 // 使用默认值访问打招呼的方法
6 new Student().SayHi();
7
8 // 使用自定义的内容打招呼
9 new Student { Name = "阿何", Language = LanguageType.English }.SayHi();
10 Console.ReadKey();
11
12 /*
13 *
14 * 输出 大家早上好! 我是Johness
15 * Morning! I'm 阿何
16 *
17 */
18 }
19 }
我们说,在委托变量的内部,我们可以set,get,invoke(设置、获得、调用)委托。那么我们如果要求委托变量的访问权限受到限制,在外部不能调用只能设置呢?
可能有的朋友要问:为什么要让外界不能调用,还有,就算是要使外界不能调用也可以直接用没有get的访问器吧。
实际上,我们并不希望外界调用不属于它的委托,我们值希望外界将方法绑定到委托,而真正调用委托的对象应该是该委托真正属于的对象,它会在适当的时机调用委托。
举一个简单的例子:我们在写窗体代码的时候写的是事件,不知道大家有没有观察:我们写好的事件(其实是方法)被绑定到了控件属于的委托,那么,我们在窗体代码写的方法,并不是在方法将要绑定到的控件,而且我们指定了什么时候执行委托吗?没有,我们也不可能执行……
也就是说:一个带有委托的对象,它希望外界可以向它的委托注册(或绑定方法),但是它会保留什么时候执行该委托的权利,这一点并不为外界所知。原因在于:我们并不知道该委托什么时间该发生(比如鼠标点击,只有Windows才知道)。
(为了方便讲解事件,上述及即将介绍的都只是委托的使用之一。意思是,委托在做事件这方面才是要求外面只能set)
我们可以利用普通封装来保证上面要求的实现:
1 using System;
2
3 namespace DelegateEG
4 {
5 public class Program
6 {
7 public static void Main(string[] args)
8 {
9 Student stu = new Student();
10 // 注意,这里的=即委托的+=
11 stu.Test = new TestHandler(bMethod);
12
13 // 通过调用方法让对象在内部调用Test
14 stu.OnTest();
15 Console.ReadKey();
16 /*
17 * 输出:
18 *
19 * 这是委托所在对象的方法
20 * 这是Program中的静态方法
21 */
22 }
23
24 public static void bMethod()
25 {
26 Console.WriteLine("这是Program中的静态方法");
27 }
28 }
29
30
31 // 委托
32 public delegate void TestHandler();
33
34 class Student
35 {
36 // 委托和其封装
37 private TestHandler _test;
38 public TestHandler Test
39 {
40 // 没有get
41
42 // 注意:此处set里默认是但设置,我们略作修改
43 // 默认为 set { _test = value; }
44 set { _test += value; }
45 }
46
47 // 调用委托
48 public void OnTest()
49 {
50 this._test();
51 }
52
53 // 测试方法
54 public void aMethod()
55 {
56 Console.WriteLine("这是委托所在对象的方法");
57 }
58
59 // 构造方法
60 public Student()
61 {
62 Test = new TestHandler(aMethod);
63 }
64 }
65 }
实际上在WinForm事件处理时,真正调用委托的就是On的一类方法,但是这一般也不会被外界处理。
这样说吧,外界值关心当某个事件发生(在某个情况下)时要委托处理什么,而委托所在对象只关心它什么时候该工作。
我们就是外界。
比如,学生看到老师是一个委托,这个委托代表了向老师问好的一类方法,当发生了学生遇到老师,这个时候委托工作。我们可以事先告诉学生:你应该说“老师好”。我们就给学生看到老师这个委托绑定了方法,但是我们不知道学生什么时候会碰到老师,但是学生是关心的,他遇到老师之后就会执行我们的方法。这个时间学生也不能掌握,但是他有执行或者不执行的权利。我们只有简单的权利:教他怎么向老师问好。
那么以上过程在代码中使用普通委托和封装可以如下写法:
1 using System;
2
3 namespace DelegateEG
4 {
5
6 public delegate void SayHiToTeacherHandler();
7
8 class Student
9 {
10 private SayHiToTeacherHandler _sayHiToTeacher;
11 public SayHiToTeacherHandler SayHiToTeacher
12 {
13 set { _sayHiToTeacher += value; }
14 }
15
16 public void OnMeetTeacher()
17 {
18 if (_sayHiToTeacher != null)
19 {
20 _sayHiToTeacher();
21 }
22 }
23 }
24
25 public class Program
26 {
27 public static void Main(string[] args)
28 {
29 Student stu1 = new Student();
30 stu1.SayHiToTeacher = new SayHiToTeacherHandler(SayHi);
31
32 Console.ReadKey();
33 }
34
35 public static void SayHi()
36 {
37 Console.WriteLine("遇到老师了,向老师问好:老师好");
38 }
39 }
40 }
首先,我们定义学生类,该对象拥有向老师问好的方法,但是由于要应付可能的变化,我们不用方法而使用委托。
1 // 委托
2 public delegate void SayHiToTeacherHandler();
3
4 private SayHiToTeacherHandler _sayHiToTeacher;
5 public SayHiToTeacherHandler SayHiToTeacher
6 {
7 set { _sayHiToTeacher += value; }
8 }
我们告诉学生应该怎么向老师问好:
1 public class Program
2 {
3 public static void Main(string[] args)
4 {
5 Student stu1 = new Student();
6 stu1.SayHiToTeacher = new SayHiToTeacherHandler(SayHi);
7
8 Console.ReadKey();
9 }
10
11 public static void SayHi()
12 {
13 Console.WriteLine("遇到老师了,向老师问好:老师好");
14 }
15 }
遇到老师的时候,问好:
1 public void OnMeetTeacher()
2 {
3 if (_sayHiToTeacher != null)
4 {
5 _sayHiToTeacher();
6 }
7 }
在强调一遍:这个方法发生的时间是学生和我们都没办法知道的。
那么,以上的写法很烦是吗?我们有简写吗,呵呵,答案是肯定的。
当当当,Look:
1 using System;
2
3 namespace DelegateEG
4 {
5 public delegate void SayHiToTeacherHandler();
6
7 class Student
8 {
9 // 修改一
10 public event SayHiToTeacherHandler SayHiToTeacher;
11
12 public void OnMeetTeacher()
13 {
14 SayHiToTeacher.Invoke(); // 事件调用
15 // SayHiToTeacher();
16 }
17 }
18
19 public class Program
20 {
21 public static void Main(string[] args)
22 {
23 Student stu1 = new Student();
24 // 修改二
25 stu1.SayHiToTeacher += new SayHiToTeacherHandler(SayHi);
26
27 Console.ReadKey();
28 }
29
30 public static void SayHi()
31 {
32 Console.WriteLine("遇到老师了,向老师问好:老师好");
33 }
34 }
35 }
大家仔细看看,我改的两个地方,自己领悟一下。
event关键字用于封装委托,封装之后是一个“事件”,事件能在类外部被绑定一个或多个方法,而只能在类内部被调用。
即离开事件所属的对象,事件只能出现在+=或-=的左边。
2012-04-03 22:56:03
上午写了很久,但是不小心点了关闭……我就懒得写很多东西了。直接贴出来一个例子吧。
热水器的例子(改编自网络)
1 using System;
2
3 namespace EventEG
4 {
5 class Program
6 {
7 static void Main(string[] args)
8 {
9 Heater heater = new Heater();
10 // 设置报警方式
11 heater.BoilEvent += new BoilEventHandler(Alarm);
12 // 我们只能控制烧水(即开始加热)
13 // 何时报警不是我们掌控的
14 heater.Boil();
15
16 Console.ReadKey();
17 // 输出
18 /*
19 * 水快烧开了!
20 * 水快烧开了!
21 * 水快烧开了!
22 * 水快烧开了!
23 * 水快烧开了!
24 * 水快烧开了!
25 */
26 }
27 // 预设的报警方式
28 public static void Alarm()
29 {
30 Console.WriteLine("水快烧开了!");
31 }
32 }
33
34 // 委托
35 public delegate void BoilEventHandler();
36
37 class Heater
38 {
39 // 加热(烧水)事件
40 public event BoilEventHandler BoilEvent;
41 // 水温
42 private int _temperature;
43 // 加热方法
44 public void Boil()
45 {
46 for (_temperature = 0; _temperature < 101; _temperature++)
47 {
48 // 加热使水温升高,当到达一定程度使引发事件
49 if (_temperature >= 95)
50 {
51 OnBoilEvent();
52 }
53 }
54 }
55
56 // 引发事件
57 void OnBoilEvent()
58 {
59 // 发生事件
60 BoilEvent.Invoke();
61 // BoilEvent();
62 }
63 }
64 }
我很惆怅啊!得想个办法……
2012-04-04 11:54:04