<center>C#委托与事件的总结</center>
1.概述
委托与事件是C# 1.0版本的新特性,与C# 1.0版本对应的是.NET Framework 1.0,发布于2002年1月,在这个版本中C#还提供了编程语言基础特性:
- Classes:面向对象特性,支持类类型
- Structs:结构
- Interfaces:接口
- Events:事件
- Properties:属性,类的成员,提供访问字段的灵活方法
- Delegates:委托,一种引用类型,表示对具有特定参数列表和返回类型的方法的引用
- Expressions,Statements,Operators:表达式、语句、操作符
- Attributes:特性,为程序代码添加元数据或声明性信息,运行时,通过反射可以访问特性信息
- Literals:字面值(或理解为常量值),区别常量,常量是和变量相对的
本篇博文将通过一个事例来说明委托与事件,其中包括了委托的定义,委托的使用,事件是如何产生(在这之中将说明事件与委托的关系),以及委托与事件特性有什么优势,为什么要去定义这一特性,我们为什么要去使用这一特性。
本篇博文是对大牛们的博客与书籍的学习总结,可能会有雷同之处,如若侵权,烦请告知,立即删除。
本篇博文只是一名初学者的皮毛见解,仅供以学习交流,如有错误之处,烦请指出校正,如若希望更深入的学习或者得到更清晰的描述,请前往各位大牛的博客,链接地址将在参考目录中给出。
2.委托
2.1 什么是委托
在生活中我们遇到过许多委托,子学校里不想去上课,我们也许会委托室友答到,在法庭上,辩护人会委托律师为其进行辩护。生活中有许多这样的例子,单子啊C#中,委托是什么样子呢?
委托在C/C++中可以理解为函数指针,党调用这个指针时,便执行了这个指针所指向的函数,在C#中可以理解为函数的一个包装,调用委托时便执行了该函数。
C/C++通过函数指针获得了函数的入口地址,通过调用函数指针来实现对函数的操作
要怎樣去理解呢?我們以A同學委託B同學答到這一事例來模擬一下,A同學是一個類class A
,有一個答到方法public void Dadao(string name)
,答到方法有一個參數name提供了A同學的名字,現在A同學想睡懶覺不想去上課了,他委託了室友B去答到,黨老師叫到A的時候,B便會說:“A,到!”(我們只用調用B便會得到調用A類中Dadao方法的結果),具體代碼參見代碼清單 2-1
public class A
{
public void Dadao(string name)
{
Console.WriteLine("A,到!");
}
}
public delegate void DadaoHandler();
class MainEntry
{
static void Main()
{
DadaoHandler B =
new DadaoHandler(new A().Dadao);
B("A");
}
}
代码清单 2-1
或許這個事例說明的有點彆扭,不妨換個事例,還是同學A與同學B之間的故事,B是我們高中班上的美女小組長,A是我們的猥瑣後桌。
我們每天上學要做的第一件事是什麼?對了,交作業!美麗的清晨,東方才微微泛出陽光,空氣中充滿了泥土的清香,美女小組長B跟猥瑣后桌B說:“那誰,emmm,帥哥,等下幫我收下作業唄,我去看體育生學長晨練了~”,那誰非常高興的答應了,老師來了說把作業收起來,我們的A哥便馬上屁顛屁顛的去收作業了,收的時候還不忘向你嘚瑟下。
這個事例中,美女小組長B委託了猥瑣后桌A收作業。B中有收作業的方法,當老師調用A時,便調用了B中的收作業方法。
2.2 委託的使用
下面將會通過模擬接待外賓時,廚師、服裝師所做的準備。我們有一個廚師類,一個服裝師類,廚師類有個烘焙蛋糕的方法,服裝師類有個穿正裝的方法,我們聲明了一個外賓接待委託。我們將以這個事例來說明委託的聲明、實例化以及綁定,详细代码请参考代码清单2-2。
2.2.1 委託的申明
前面我們說過,委託是方法的一個包裝。它與方法有著相同的方法簽名和返回類型。
[訪問修飾符] delegate return_type handler_name(args_type args);
現在我們的廚師class Cook
有一個烘焙蛋糕的方法void BakeCoke(string name);
,我們的服裝師class Dress
有一個穿正裝的方法void FormalWear(string name);
其中name是外賓的名字。現在我們為這兩個方法創建一個委託,我們可以使用一下申明:
public delegate void ForeignHandler(string name);
方法簽名相同:方法的參數個數、順序、類型相同
2.2.2 委託的實例化
現在我們將ForeignHandler
實例化,將需要委託的方法作為構造函數參數來實例化委託。
ForeignHandler fg_handler = new ForeignHandler(new Cook().BackCoke);
當然,靜態方法也可以進行委託,現在我們將烘焙蛋糕的方法設置為靜態方法static void BakeCoke(string name);
ForeignHandler fg_handler = Cook.BakeCoke;
2.2.3 方法绑定委托
绑定是委托的一个特性,它能让我们在一个委托事例中绑定多个方法,不需要去为每个方法去实例化一个委托。绑定符号为+=
,当然我们可以使用-=
来解除绑定。当将方法绑定到委托上后,调用委托时,绑定在这个委托上的所有方法都会被执行。
fg_handler += new Dress().FormalWear;
2.3 深入理解委托
现在我们知道了怎样去使用委托,但我们要怎样去理解委托?其实,大家应该有了一个认识,委托其实就是一个类,我们对委托的一些操作其实就是对类的操作,只是有一些特殊之处。
现在我们不使用委托来实现包装Cook.BakeCoke
方法,我们需要怎样做?我们需要定义一个方法void function();
,然后需要一个参数来接收BakeCoke
方法,所以我们的方法变为了void function(*** MakeBakeCoke);
,因为BakeCoke
方法中有一个name参数,所以我们需要为function
添加一个string类型的参数,最终,这个方法便如下所示:
public void function(string name, *** MakeBakeCoke)
{
MakeBakeCoke(name);
}
但是是,我们不知道MakeBakeCoke的类型,也就是说***具体应该怎样写?现在我们有了委托,一切问题都解决了,我们声明一个委托类型,然后将委托类型写在这就可以了。
2.4 为什么要使用委托
委托能够有效的为代码解耦,提高代码的可扩展性,具体如何尚在学习之中,在参考目录中有对于代码耦合度的相关链接,大家可以前往进行学习。
2.5 代码清单
namespace Event
{
public delegate void ForeignHandler(string name);
public class Cook
{
public void BakeCoke(string name)
{
Console.WriteLine("Your Majesty,We'll baking coke for " + name);
}
}
public class Dress
{
public void FormalWear(string name)
{
Console.WriteLine("Yor Majesty, You'll wear formal to meeting " + name);
}
}
public class MeetingForeignManage
{
public void MeetingForeign(string name, ForeignHandler make_meeting)
{
make_meeting(name);
}
}
class Program
{
static void Main(string[] args)
{
// 實例化委託
// ForeignHandler fg_handler;
// fg_handler = new CookServant().BakeCoke;
ForeignHandler fg_handler =
new ForeignHandler(new Cook().BakeCoke);
// 綁定委託
fg_handler += new Dress().FormalWear;
// 調用委託
MeetingForeignManage mt_fg = new MeetingForeignManage();
mt_fg.MeetingForeign("Jorge King", fg_handler);
Console.Read();
}
}
}
代码清单 2-2
3.事件
3.1 事件与委托的关系
在本小节中,我们将从事件的由来来慢慢理清事件与委托之间的关系,在理清这层关系之后我们将对委托与事件的理解更上一个层次,在日后代码量增多后,也将会对委托与事件的应用场景,委托与事件特性优势会有自己的理解。
3.3.1 事件的由来
通过代码清单2-2,我们大概知道了委托是如何使用的,但面向对象的程序是有封装性这一特性的,既然讲究封装性,我们何不把在Main
函数中定义的委托对象封装到MeetingForeignManage
类中呢?现在我们进行这一操作,于是,MeetingForeign
类和Main
函数代码变成了如代码清单3-1所示。
public class MeetingForeignManage
{
public ForeignHandler fg_handler; // MeetingForeignManage类封装了委托
public void MeetingForeign(string name, ForeignHandler make_meeting)
{
make_meeting(name);
}
}
class Program
{
static void Main(string[] args)
{
MeetingForeignManage mt_fg = new MeetingForeignManage();
mt_fg.fg_handler = new Cook().BakeCoke;
mt_fg.fg_handler += new Dress().FormalWear;
mt_fg.MeetingForeign("Jorge King", mt_fg.fg_handler);
Console.Read();
}
}
代码清单 3-1
这样运行结果是没问题,但是有条语句却很奇怪mt_fg.MeetingForeign("Jorge King", mt_fg.fg_handler);
我们在调用这个方法时,为什么还要把MeetingForeignManage
类中的成员再传递到该类中?所以我们可以代码进行如代码清单 3-2所示的改进。
public class MeetingForeignManage
{
public ForeignHandler fg_handler;
public void MeetingForeign(string name)
{
if (fg_handler != null)
{
fg_handler(name);
}
}
}
class Program
{
static void Main(string[] args)
{
MeetingForeignManage mt_fg = new MeetingForeignManage();
mt_fg.fg_handler = new Cook().BakeCoke;
mt_fg.fg_handler += new Dress().FormalWear;
mt_fg.MeetingForeign("Jorge King");
Console.Read();
}
}
代码清单 3-2
这样运行是可以得到我们想要的结果,但我们却要面对一个显而易见的问题,那边是fg_handler
字段竟然是public
公开的!这样导致了我们的任何MeetingForeignManage
实例都可以对fg_handler
进行操作,我们为了封装却严重了违反了封装的原则,这真是可笑的!但是我们能将fg_handler
设置为private
私有的吗?这显然也是不可行的,因为委托的目的就是把暴露在类的客户端进行的方法进行注册,设置为私有了,客户端无法调用它,那它还有什么用呢?所以C#为我们定义了事件这一特性。
好了,让我们把代码再改一下,改成和代码清单 3-3一样。
public class MeetingForeignManage
{
public event ForeignHandler fg_event;
public void MeetingForeign(string name)
{
fg_event(name);
}
}
class Program
{
static void Main(string[] args)
{
MeetingForeignManage mt_fg = new MeetingForeignManage();
mt_fg.fg_event += new Cook().BakeCoke;
mt_fg.fg_event += new Dress().FormalWear;
mt_fg.MeetingForeign("Jorge King");
Console.Read();
}
}
代码清单 3-3
3.3.2 委托与事件的关系
通过对事件的由来的分析,我们可以这样理解委托与事件,委托可以类比为字段,事件可以理解为属性,也就是说事件对委托的封装可以类比为属性对字段的封装,这两种封装模式是完全一样的,不同的是事件对委托的封装实在编译阶段完成,是不可自定义的封装,而属性对字段的封装是在coding阶段进行的,是可自定义的封装。
3.2 订阅者与发布者(监视对象与监视者)
现在我们用Observer设计模式来完成我们的代码,我们有一位君主class Emperor
,今天他有一件重要的事去做,那边是接见外宾,接见外宾时他要把自己打扮的美美的,毕竟面子事大,所以他需要服装师class Dress
来帮他选衣服public void FormalWear(string name);
,另外还要准备可口又不失优雅的食物来招待外宾,毕竟面子事大,所以他需要一个*的厨师class Cook
来帮助他烘焙可口的蛋糕public void BakeCoke(string name);
,现在爱美的君主要准备接见外宾了,他触发了接待外宾的事件public void MeetingWithForeignGuests(string fg_name);
,我们一直关注着君主动态的厨师和服装师准备开始烘焙蛋糕和为君主穿上正装了(注册了事件的方法fg_coming.fg_event += new Cook().BakeCoke;
fg_coming.fg_event += new Dress().FormalWear;
),在这个代码中,君主是发布者(监视对象),厨师和服装师是订阅者(监视者),当事件一触发,他们便会作出反应。具体代码参见代码清单 3-4
namespace Event
{
public delegate void ForeignHandler(string name);
// 订阅者(监视者)
public class Cook
{
public void BakeCoke(string name)
{
Console.WriteLine("Your Majesty,We'll baking coke for " + name);
}
}
// 订阅者(监视者)
public class Dress
{
public void FormalWear(string name)
{
Console.WriteLine("Yor Majesty, You'll wear formal to meeting " + name);
}
}
// 发布者(监视对象)
public class Emperor
{
public event ForeignHandler fg_event;
public void MeetingWithForeignGuests(string fg_name)
{
Console.WriteLine("We need meeting with foreign guests.");
if (fg_event != null)
{
fg_event(fg_name);
}
}
}
class Program
{
static void Main(string[] args)
{
string fg_name = "Jorge King";
Emperor fg_coming = new Emperor();
// 使Cook和Dress订阅事件
fg_coming.fg_event += new Cook().BakeCoke;
fg_coming.fg_event += new Dress().FormalWear;
fg_coming.MeetingWithForeignGuests(fg_name);
Console.Read();
}
}
}
代码清单 3-4
4.參考目錄
- C#個版本特性
- 關於耦合度的討論
- 委託與事件的理解
- 張子陽對委託與事件講解
- 關於委託寫法演變的討論
- 《Learning hard C#學習筆記》 第八章
- 《C#高級編程(第九版)》 第八章
對以上文章表示由衷感謝。