代码改变世界

读书笔记:理解委托与事件

2011-06-01 09:49  littlelion  阅读(426)  评论(3编辑  收藏  举报

今天是儿童节,在这个普天同庆的日子里,祝所有的小朋友以及尚有一颗童真的心的大人们节日快乐吧。也祝自己节日快乐。

说完了开场白,进入正题吧。

首先说委托,什么是delegate,委托从本质上来讲就是一个类型,跟class很像,比如说,我们可以把阿森纳,曼联,利物浦...抽象成一个叫做club的类,同样,也可以把散步,爬楼梯,跳绳...归结为一个叫做sportDelegate的委托。能抽象成一个class的都是一些具有内部共性的对象,而能归结为一个delegate的则是一些方法。

 

View Code
class Program
{
publicdelegatevoid hobbyDelegate(string name);

publicstaticvoid watchFootballMatch(string name)
{
Console.WriteLine(name
+" likes watching football match");
}
publicstaticvoid playRubikCube(string name)
{
Console.WriteLine(name
+" likes playing Rubik's Cube");
}
publicstaticvoid readBooks(string name)
{
Console.WriteLine(name
+" likes reading books");
}

publicstatic hobbyDelegate my_hobby;

staticvoid Main(string[] args)
{
my_hobby
=new hobbyDelegate(watchFootballMatch);
my_hobby
+=new hobbyDelegate(playRubikCube);
my_hobby
+=new hobbyDelegate(readBooks);
my_hobby(
"cloverying");
}
}

从上述代码中可以看出委托类似于class的影子,我们可以定义一系列跟委托有着相同签名的方法(相同的返回值类型,相同的参数列表),将这些方法当做参数来传递,所以,委托的作用就相当于一个函数指针。

 

publicstaticvoid watchFootballMatch(string name)
{
Console.WriteLine(name
+" likes watching football match");
}

将watchFootballMatch当做参数传递给hobbyDelegate构造器,当调用my_hobby时,同时也调用了watchFootballMatch。

同时可以看到,在main函数中,watchFootballMatch方法,playRubikCube方法和readBooks方法都绑定(+=)到了my_hobby上,这种将多个方法绑定到同一个委托变量的形式叫做多播委托。同时也形成了一个委托链。在程序执行时,依次顺序执行就OK了。

因此,上述代码将输出:


如果要解除同某一个方法的绑定,只要利用 -= 操作符就可以了。代码如下:

my_hobby -= new hobbyDelegate(readBooks);

执行了以上代码后,将不会输出 cloverying likes reading books

绑定和解除绑定的执行机制可以看一下IL代码:

//省略的部分

IL_0023: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine
(
class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)

//省略的部分

IL_0063: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove
(
class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)

从IL码可以看出,绑定和解除绑定就是执行了Combine和Remove操作。

我对IL代码了解的不是很多,可以说皮毛都算不上,只能根据IL代码勉强看出具体的执行过程,所以就不从IL代码上来分析委托的构造函数以及回调方法这些东西了。但是我觉得多看看IL代码还是有好处的,可以对内部具体的执行机制有个本质上的理解。

 

了解了委托究竟是什么东西后,再来看看大名鼎鼎的匿名方法.

首先要说明的很重要的一点就是:我今年3月份开始学习C#,所以没那个运气经历.NET 1.X到.NET 2.0 到.NET 3.0的历史变迁,因此一上来,我就要学习匿名方法和Lambda表达式,在我还不知道委托是什么玩意的时候,这些术语就满篇飞了,因此我晕了好长时间。但我不能让自己一直晕下去,所以我要在痛苦中享受delegate的乐趣~~

首先来思考一个问题,为什么要使用匿名方法。任何东西的诞生都是有原因的,尤其像这么一个重要的东东。这个问题真的很纠结,对于我这样一个初学者来说,没有任何开发的经验,单纯从代码来看,我只看到了使用匿名方法更加简洁,更加好理解这一好处,在这里,恳请各位前辈给小的指点一下吧~~

以下是使用了匿名方法的代码

 

View Code
class Program
{
publicdelegatevoid hobbyDelegate(string name);
staticvoid Main(string[] args)
{
hobbyDelegate watchFootballMatch
=delegate (string name)
{
Console.WriteLine (name
+" likes watching football match");
};
hobbyDelegate readBooks
=delegate(string name)
{
Console.WriteLine(name
+" likes reading books");
};
string myname ="ying";
watchFootballMatch(myname);
readBooks(myname);
}
}

输出结果为:



很明显,这样写把方法和委托实例watchFootballMatch以及readBooks直接关联起来了,不用在每次都new一下委托了。

 

说到这里,可以结合示例代码理解一下匿名方法的定义了。

在Mark Michaelis的《C# 本质论》中,对匿名方法的定义说明如下:

所谓匿名方法,就是没有实际方法声明的委托实例,或者说,他们的定义是直接内嵌在代码中的。

上面的代码中,委托实例watchFootballMatch和readBooks事先并没有做任何声明,而是直接给出了他们的定义,这样做最直观的一点就是代码变得简洁了,而且更加易读,嗯,代码易读比什么都珍贵。

下面再来看看更简洁的Lambda表达式,先看代码

 

class Program
{
publicdelegatevoid hobbyDelegate(string name);
staticvoid Main(string[] args)
{
hobbyDelegate watchFootballMatch
= (string name) => Console.WriteLine(name +" likes watching football match");
string myname ="ying";
watchFootballMatch(myname);
}
}

Lambda表达式比匿名方法更加直接了,引入“=>”后,就这么直接写出了方法实现,比上一段代码更清晰了。

从.NET各个版本对委托写法的进化充分印证了一点,世界是“懒人”创造的,代码越来越简洁,越来越看着顺眼了。

 

说完了委托,再来看看委托的管家:事件event.

简单来说,事件就是对委托的封装,从而保证了委托的安全性。那么很容易理解,事件一定是建立在委托机制上的。

很据《你必须知道的.NET》中对事件的定义过程,我写了如下代码

 

View Code
publicclass Hobby
{
//First,定义一个HobbyEventArgs,其作用是用于存放事件引发时向事件处理程序传递的状态信息
//其实就是一个事件参数类型
publicclass HobbyEventArgs : EventArgs
{
//定义了一个只读字段,一个构造函数
publicreadonlystring str_name;
public HobbyEventArgs(string name)
{
this.str_name = name;
}
}

//second, 声明事件委托
//包含两个参数:事件发送者对象 事件参数类对象
publicdelegatevoid HobbyEventHandler(object sender, HobbyEventArgs e);
//注:这里有一个命名规范,事件委托都是以EventHandler结尾的

//third,定义事件成员,作用是进行外部绑定
publicevent HobbyEventHandler my_hobby;

//fourth, 提供受保护的虚方法,因为是virtual,自然可以由子类来override,这样做可以拒绝监视
protectedvirtualvoid AboutHobby(HobbyEventArgs e)
{
if (my_hobby !=null)
{
my_hobby(
this, e);
}
}

//fifth 调用该方法,从而得知hobby有哪些
publicvoid HobbyIS(string Name)
{
HobbyEventArgs e
=new HobbyEventArgs (Name);
AboutHobby(e);
//通知所有的事件注册者
}
}

以上对事件的定义代码完全是参照《你必须知道的.NET》一书一步一步照搬下来的,具体每一步的作用注释中已经写得很清楚了。

有了事件的定义后,还需要一个事件的触发程序,不然你怎么去实现这个事件呢是吧。

 

View Code
publicclass HobbyManager
{
//定义消息通知方法
publicvoid watchFootballMatch(object sender, Hobby.HobbyEventArgs e)
{
Console.WriteLine(e.str_name
+" likes watching football match");
}

publicvoid readBooks(object sender, Hobby.HobbyEventArgs e)
{
Console.WriteLine(e.str_name
+" likes reading books");
}
}

OK,这就是事件的触发者,watchFootballMatch & readBooks。

 

下面是事件处理程序

 

class Program
{
staticvoid Main(string[] args)
{
Hobby myhobby
=new Hobby();
HobbyManager manager
=new HobbyManager();
myhobby.my_hobby
+= manager.watchFootballMatch;
myhobby.my_hobby
+= manager.readBooks;
myhobby.HobbyIS(
"ying");
}
}

输出结果如下:


 

说到这里,我想像我一样以前从来没有接触过委托和事件的孩子们大概能知道delegate和event表面上是个什么东西了吧,我写的这些,完全谈不上什么个人见解,我也没那么资格,只是记录一下自己的学习过程,也加深一下理解,遗憾的是我还没有应用过。要说简单接触也就是在应用控件的时候触发一个button事件,嗯,这算是一个事件的应用了。

昨天一个老师说,我目前会用而且不会用错就OK了,等到以后再了解背后的东西,其实说实话,我还真不清楚什么是背后的东西,我也不清楚自己怎么才算是会用,什么才不会用错,问一个同学,从来就没有使用过委托和事件,我真不知道目前的阶段学习这些有没有多余,当然多余是指定不会的,毕竟学了比不学强,我只是突然觉得有些迷茫了。。。