委托与事件
委托
定义委托的语法和定义方法比较相似,只是比方法多了一个关键字delegate ,我们都知道方法就是将类型参数化,所谓的类型参数化就是说该方法接受一个参数,而该参数是某种类型的参数,比如int、string等等;而委托是将方 法参数化,说了上面的那个类型参数化之后,相信你也能猜到方法参数化的意思了,对,就是将方法作为一个参数传到一个委托中。
首先来看看声明委托的语句:
public deletate void MyDelegate();
public:访问修饰符 delegate:关键字 void:返回类型 MyDelegate:委托名称 ( ):参数列表
看到声明大家会想了,为什么该委托的返回值,参数列表要这样的,我不能返回一个 string,一个int么?我不能给委托加几个参数么? 答案是:当然可以,但委托的定义是相对于方法来说的,因为得你的委托最终是要来注册方法的,而你的方法是具有某种签名的,所以你要给怎样签名的方法来声明 一个委托,该委托就要和该方法具有同等的签名,就类似于你用一个int 类型的变量去接受一个string类型的值,显然是不行的(个人理解)....
* 委托只要定义就可以了,我们并不需要关心他的实现
委托的使用
注册委托有两种方法:
第一种:直接将方法赋值[=]或者用“+=” 给一个委托==>委托名 =[+=] 方法名
第二种:委托本质也是一个类,只是一个特殊的类,所以我们也可以实例化一个委托对象通过委托构造函数来注册委托==》委托名 对象名= new 委托名(方法名)
了解了委托的声明和使用,我们就可以来看小例子来加深理解了
首先看看界面:
界面上就是简单的四个按钮 两个属于委托,两个属于事件,都是一个用来执行,一个用来干扰,以便于来理解委托事件
然后看后台代码,首先我定义了一个Test类,声明委托,实例了委托,还声明了事件,写了个方法用来触发事件,代码如下:
2 {
3 //声明一个委托
4 public delegate void MyDelegate();
5
6 //创建一个委托实例
7 public MyDelegate myDel;
8 //声明一个事件
9 public event MyDelegate EventMyDel;
10
11 //事件触发机制(必须和事件在同一个类中) 外界无法直接用EventMyDel()来触发事件
12 public void DoEventMyDel()
13 {
14 EventMyDel();
15 }
16 }
然后为了方便测试,我还定义了三个方法在Form1类,代码如下:
2 public void Fun_A()
3 {
4 MessageBox.Show("A 方法触发了");
5 }
6
7 //方法B
8 public void Fun_B()
9 {
10 MessageBox.Show("B 方法触发了");
11 }
12 //方法C
13 public void Fun_C()
14 {
15 MessageBox.Show("C 方法触发了");
16 }
然后在页面Page_Load时注册委托,将方法A和方法B挂载到委托链上,代码如下:
2 private void Form1_Load(object sender, EventArgs e)
3 {
4
5 //注册委托(挂载方法)
6 test.myDel += Fun_A;
7 test.myDel += Fun_B;
8
9 //注册事件(挂载方法)
10 //test.EventMyDel += Fun_A;
11 //test.EventMyDel += Fun_B;
12 }
然后给[执行委托]按钮添加Click事件,代码如下:
2 private void button1_Click(object sender, EventArgs e)
3 {
4 //执行委托链中的方法
5 test.myDel();
6 }
显示效果:
2 private void button2_Click(object sender, EventArgs e)
3 {
4 //给委托赋 null 值
5 test.myDel = null;
6
7 //给委托挂载上C方法
8 test.myDel += Fun_C;
9
看看结果会怎么样,哎,发现就只有C方法被触发了:
大家想想,为什么会这样呢??为什么A,B方法不执行了呢??这是因为原来的委托链 是已经挂载了A,B两个方法了,但这时突然被干扰了下,又附加了一个null对象,破坏了原有的委托链了,但这又说明了什么呢?因为委托本质就是一个类, 它包含一对有用的字段,第一个字段是存放该对象的引用,第二个字段存放一个方法的指针,所以我们可以把委托理解成一个指向函数的指针,当一个方法作为参数 赋值给一个委托的时候,该委托就指向了该方法的首地址,即方法名,所以当我们给委托注册A,B两个方法,该委托就同时指向了A,B两个方法的首地址,但这 是又突然给委托赋值了,且赋值了一个null对象,注意这里用的是赋值符号[=],这就是说让该委托清除原有的指针指向,此时指向一个null,之后又给 委托注册了C方法,所以此时委托即指向null,又指向了C方法的首地址,这就是为什么运行时只会看到C方法被触发的原因了!
那就是说现在的委托变得不安全了,哪天一个项目中给委托注册了很多方法,但突然被干扰了下,前面的注册都失效了,那我们前面做的工作不是白做了,那有没有办法可以防止这种干扰呢??答案是当然有,相信聪明的你也应该猜到了,这时就是事件该上场的时候了。
事件
事件就是一个特殊的委托,委托和事件就类似于字段和属性的关系,事件是对委托做了一个封装(这是个人理解)
先看看声明一个事件:
public Event MyDelegate EventMyDel;
public:访问修饰符 Event:关键字 MyDelegate:委托 EventMyDel:事件名称
接下来让我们来看看我们用反编译工具来反编译下该项目的exe可执行文件,看看当我们给事件注册上方法时,事件内部在做什么,附上反编译代码:
从 反编译中就可以充分看出,事件的本质就是一个委托,它的内部仍然是用一个委托,该委托的名称的方法就只有和事件名首字母的大小写不同而已,该委托来负责接 收事件,然后通过add,remove方法来实现委托的注册和删除,即当我们给委托注册一个方法时,内部就调用add方法,通过 Delegate.Combine()方法来实现将方法附加到委托链上,当我们删除委托链上的一个方法时,即内部调用remove方法,通过 Delegate.Remove()方法,将该方法从委托链上移除,所以通过反编译工具,我们就可以清楚的知道事件内部的实现代码了。
接下来看看事件的效果,首先在Page_Load事件中注册事件,我们将A,B方法注册到事件上,代码如下:
2 private void Form1_Load(object sender, EventArgs e)
3 {
4
5 ////注册委托(挂载事件)
6 //test.myDel += Fun_A;
7 //test.myDel += Fun_B;
8
9 //注册事件
10 test.EventMyDel += Fun_A;
11 test.EventMyDel += Fun_B;
12 }
2 private void button3_Click(object sender, EventArgs e)
3 {
4 //执行事件触发方法
5 test.DoEventMyDel();
6 }
看看效果,如图:
2 private void button4_Click(object sender, EventArgs e)
3 {
4 //注册干扰事件
5 test.EventMyDel = null;
6
说明了什么,,说明我们没法对事件用赋值[=]号来进行注册,这就避免了破坏直接委托链的指针指向了,,但你会想了,那如果我们用附加[+=]上一个null对象呢?会干扰到么??好的,,我们实施下,修改代码,如下:
2 private void button4_Click(object sender, EventArgs e)
3 {
4 //注册干扰事件
5 test.EventMyDel += null;
6
7 //注册C方法
8 test.EventMyDel += Fun_C;
9
10 }