`
isiqi
  • 浏览: 15946079 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

C#/.NET中委托和事件的机制和应用

阅读更多

本文摘自人民邮电出版社出版的《Windows Forms程序设计》(Chris Sells著,荣耀、蒋贤哲译)。通过一个栩栩如生的虚构故事解释了C#/.NET中委托和事件的机制和应用。

1 委托

从前,在南方的一个异国他乡,有一个叫Peter的勤劳的工人,他对老板(boss)百依百顺,然而他的boss却是个卑鄙多疑的家伙,他坚 持要求Peter不断汇报工作进展。由于Peter不希望被boss盯着干活,于是他向boss承诺随时汇报工作进度。Peter通过如下所示的类型化的 引用(typed reference)定期回调boss来实现这个承诺:

c# 代码
  1. classWorker{
  2. publicvoidAdvise(Bossboss){this.boss=boss;}
  3. publicvoidDoWork(){
  4. Console.WriteLine("Worker:workstarted");
  5. if(boss!=null)boss.WorkStarted();
  6. Console.WriteLine("Worker:workprogressing");
  7. if(boss!=null)boss.WorkProgressing();
  8. Console.WriteLine("Worker:workcompleted");
  9. if(boss!=null){
  10. intgrade=boss.WorkCompleted();
  11. Console.WriteLine("Workergrade="+grade);
  12. }
  13. }
  14. Bossboss;
  15. }
  16. classBoss{
  17. publicvoidWorkStarted(){/*boss不关心*/}
  18. publicvoidWorkProgressing(){/*boss不关心*/}
  19. publicintWorkCompleted(){
  20. Console.WriteLine("It'sabouttime!");
  21. return2;/*10分以内*/
  22. }
  23. }
  24. classUniverse{
  25. staticvoidMain(){
  26. Workerpeter=newWorker();
  27. Bossboss=newBoss();
  28. peter.Advise(boss);
  29. peter.DoWork();
  30. Console.WriteLine("Main:workercompletedwork");
  31. Console.ReadLine();
  32. }
  33. }


1.1 接口

现在,Peter成了一个特殊人物,他不但能够忍受卑鄙的boss,和周围的世界(universe)也建立了紧密的联系。Peter感到 universe对他的工作进程同样感兴趣。不幸的是,如果不为universe添加一个特殊的Advise方法和特殊的回调,除了保证boss能够被通 知外,Peter并不能向universe通知工作进度。Peter希望能从那些通知方法的实现中分离出潜在的通知列表,为此,他决定将方法分离到一个接 口中:

c# 代码
  1. interfaceIWorkerEvents{
  2. voidWorkStarted();
  3. voidWorkProgressing();
  4. intWorkCompleted();
  5. }
  6. classWorker{
  7. publicvoidAdvise(IWorkerEventsevents){this.events=events;}
  8. publicvoidDoWork(){
  9. Console.WriteLine("Worker:workstarted");
  10. if(events!=null)events.WorkStarted();
  11. Console.WriteLine("Worker:workprogressing");
  12. if(events!=null)events.WorkProgressing();
  13. Console.WriteLine("Worker:workcompleted");
  14. if(events!=null){
  15. intgrade=events.WorkCompleted();
  16. Console.WriteLine("Workergrade="+grade);
  17. }
  18. }
  19. IWorkerEventsevents;
  20. }
  21. classBoss:IWorkerEvents{
  22. publicvoidWorkStarted(){/*boss不关心*/}
  23. publicvoidWorkProgressing(){/*boss不关心*/}
  24. publicintWorkCompleted(){
  25. Console.WriteLine("It'sabouttime!");
  26. return3;/*10分以内*/
  27. }
  28. }


1.2 委托

不幸的是,由于Peter忙于说服boss实现这个接口,以至于没有顾得上通知universe也实现该接口,但他希望尽可能做到这一点,至少他已经抽象了对boss的引用,因此,别的实现了IWorkerEvents接口的什么人也可以得到工作进度通知。

然而, Peter的boss仍然极其不满。“Peter!”boss咆哮者,“你为什么要通知我什么时候开始工作、什么时候正在进行工作?我不关心这些事件,你 不但强迫我实现这些方法,你还浪费了你的宝贵的工作时间等我从事件中返回。当我的实现需要占用很长时间时,你等我的时间也要大大延长!你难道不能想想别的 办法不要老是来烦我吗?”

因此,Peter意识到尽管在很多情况下接口很有用,但在处理事件时,接口的粒度还不够精细。他希望能做到仅仅通知监听者真正感兴趣的事件。为此,Peter决定把接口中的方法分解为若干个独立的委托函数,每一个都好象是只包含一个方法的微型接口:

c# 代码
  1. delegatevoidWorkStarted();
  2. delegatevoidWorkProgressing();
  3. delegateintWorkCompleted();
  4. classWorker{
  5. publicvoidDoWork(){
  6. Console.WriteLine("Worker:workstarted");
  7. if(started!=null)started();
  8. Console.WriteLine("Worker:workprogressing");
  9. if(progressing!=null)progressing();
  10. Console.WriteLine("Worker:workcompleted");
  11. if(completed!=null){
  12. intgrade=completed();
  13. Console.WriteLine("Workergrade="+grade);
  14. }
  15. }
  16. publicWorkStartedstarted;
  17. publicWorkProgressingprogressing;
  18. publicWorkCompletedcompleted;
  19. }
  20. classBoss{
  21. publicintWorkCompleted(){
  22. Console.WriteLine("Better...");
  23. return4;/*10分以内*/
  24. }
  25. }
  26. classUniverse{
  27. staticvoidMain(){
  28. Workerpeter=newWorker();
  29. Bossboss=newBoss();
  30. //注意:我们已将Advise方法替换为赋值运算符
  31. peter.completed=newWorkCompleted(boss.WorkCompleted);
  32. peter.DoWork();
  33. Console.WriteLine("Main:workercompletedwork");
  34. Console.ReadLine();
  35. }
  36. }


1.3 静态订阅者

利用委托,Peter达到了不拿boss不关心的事件去烦他的目标,然而Peter还是不能够使universe成为其订阅者之一。因为 universe是一个全封闭的实体,所以将委托挂钩在实例成员上不妥的(设想一下Universe的多个实例需要多少资源)。相反,Peter需要将委 托挂钩到静态成员上,因为委托也完全支持静态成员:

c# 代码
  1. classUniverse{
  2. staticvoidWorkerStartedWork(){
  3. Console.WriteLine("Universenoticesworkerstartingwork");
  4. }
  5. staticintWorkerCompletedWork(){
  6. Console.WriteLine("Universepleasedwithworker'swork");
  7. return7;
  8. }
  9. staticvoidMain(){
  10. Workerpeter=newWorker();
  11. Bossboss=newBoss();
  12. //注意:在下面的三行代码中,
  13. //使用赋值运算符不是一个好习惯,
  14. //请接着读下去,以便了解添加委托的正确方式。
  15. peter.completed=newWorkCompleted(boss.WorkCompleted);
  16. peter.started=newWorkStarted(Universe.WorkerStartedWork);
  17. peter.completed=newWorkCompleted(Universe.WorkerCompletedWork);
  18. peter.DoWork();
  19. Console.WriteLine("Main:workercompletedwork");
  20. Console.ReadLine();
  21. }
  22. }


2 事件

不幸的是,由于universe现在变得太忙并且不习惯于注意某一个人,universe已经设法用自己的委托取代了Peter的boss的 委托,这显然是将Worker类的委托字段设为public而造成的意外的副作用。同样,如果Peter的boss不耐烦了,他自己就可以触发Peter 的委托(Peter的boss可是有暴力倾向的)

// Peter的boss自己控制一切
if( peter.completed != null ) peter.completed();

Peter希望确保不会发生这两种情况。他意识到必须为每一个委托加入注册和反注册函数,这样订阅者就可以添加或移去它们自个儿,但谁都不能 够清空整个事件列表或者触发它的事件。peter自己没去实现这些方法,相反,他使用event关键字让C#编译器帮他构建这些方法:

class Worker {
...
public event WorkStarted started;
public event WorkProgressing progressing;
public event WorkCompleted completed;
}

Peter晓得event关键字使委托具有这样的属性:只允许C#客户用+=或-=操作符添加或移去它们自己,这样就迫使boss和universe举止文雅一些:

c# 代码
  1. staticvoidMain(){
  2. Workerpeter=newWorker();
  3. Bossboss=newBoss();
  4. peter.completed+=newWorkCompleted(boss.WorkCompleted);
  5. peter.started+=newWorkStarted(Universe.WorkerStartedWork);
  6. peter.completed+=newWorkCompleted(Universe.WorkerCompletedWork);
  7. peter.DoWork();
  8. Console.WriteLine("Main:workercompletedwork");
  9. Console.ReadLine();
  10. }


2.1 获取所有结果

至此,Peter终于松了一口气。他已经设法满足了所有订阅者的需求,而且不会和特定实现紧密耦合。然而,他又注意到尽管boss和 universe都为他的工作打了分,但他只得到了一个打分。在有多个订阅者的情形下,Peter希望能得到所有订阅者的评分结果。因此,他决定“进入委 托”,提取订阅者列表,以便手工分别调用它们:

public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
int grade = wc();
Console.WriteLine("Worker grade= " + grade);
}
}
}

2.2 异步通知:触发和忽略

不料,在此期间,boss和universe被别的什么事纠缠上了,这就意味着他们给Peter的工作打分的时间被大大延长了:

class Boss {
public int WorkCompleted() {
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Better..."); return 6; /* 10分以内 */
}
}

class Universe {
static int WorkerCompletedWork() {
System.Threading.Thread.Sleep(4000);
Console.WriteLine("Universe is pleased with worker's work");
return 7;
}
...
}

不幸的是,由于Peter是同时通知每一个订阅者并等待他们打分的,这些需要返回评分的通知现在看来要占用他不少工作时间,因此,Peter决定忽略评分并且异步触发事件:

public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
wc.BeginInvoke(null, null);
}
}
}

2.3 异步通知:轮询

这个聪明的小把戏允许Peter在通知订阅者的同时能立即返回工作,让进程的线程池调用委托。然而没过多久Peter就发现订阅者给他的打分 被搞丢了。他知道自己工作做得不错,并乐意universe作为一个整体(而不仅仅是他的boss)表扬他。因此,Peter异步触发事件,但定期轮询, 以便察看可以获得的评分:

public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
IAsyncResult res = wc.BeginInvoke(null, null);
while( !res.IsCompleted ) System.Threading.Thread.Sleep(1);
int grade = wc.EndInvoke(res);
Console.WriteLine("Worker grade= " + grade);
}
}
}

2.4 异步通知:委托

不幸的是,Peter又回到了问题的起点,就像他一开始希望避免boss站在一旁边监视他工作一样。因此,Peter决定使用另一个委托作为异步工作完成时的通知方式,这样他就可以立即回去工作,而当工作被打分时,仍然可以接到通知:

public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
wc.BeginInvoke(new AsyncCallback(WorkGraded), wc);
}
}
}

void WorkGraded(IAsyncResult res) {
WorkCompleted wc = (WorkCompleted)res.AsyncState;
int grade = wc.EndInvoke(res);
Console.WriteLine("Worker grade= " + grade);
}

3 普天同乐

Peter、boss和universe最终都满意了。boss和universe都可以仅被通知其感兴趣的事件,并减少了实现的负担和不必 要的来回调用。Peter可以通知他们每一个人,而不必管需要多长时间才能从那些目标方法中返回,并仍然可以异步得到评分结果。结果得到如下完整的解决方 案:

c# 代码
  1. delegatevoidWorkStarted();
  2. delegatevoidWorkProgressing();
  3. delegateintWorkCompleted();
  4. classWorker{
  5. publicvoidDoWork(){
  6. Console.WriteLine("Worker:workstarted");
  7. if(started!=null)started();
  8. Console.WriteLine("Worker:workprogressing");
  9. if(progressing!=null)progressing();
  10. Console.WriteLine("Worker:workcompleted");
  11. if(completed!=null){
  12. foreach(WorkCompletedwcincompleted.GetInvocationList()){
  13. wc.BeginInvoke(newAsyncCallback(WorkGraded),wc);
  14. }
  15. }
  16. }
  17. voidWorkGraded(IAsyncResultres){
  18. WorkCompletedwc=(WorkCompleted)res.AsyncState;
  19. intgrade=wc.EndInvoke(res);
  20. Console.WriteLine("Workergrade="+grade);
  21. }
  22. publiceventWorkStartedstarted;
  23. publiceventWorkProgressingprogressing;
  24. publiceventWorkCompletedcompleted;
  25. }
  26. classBoss{
  27. publicintWorkCompleted(){
  28. System.Threading.Thread.Sleep(3000);
  29. Console.WriteLine("Better...");return6;/*10分以内*/
  30. }
  31. }
  32. classUniverse{
  33. staticvoidWorkerStartedWork(){
  34. Console.WriteLine("Universenoticesworkerstartingwork");
  35. }
  36. staticintWorkerCompletedWork(){
  37. System.Threading.Thread.Sleep(4000);
  38. Console.WriteLine("Universeispleasedwithworker'swork");
  39. return7;
  40. }
  41. staticvoidMain(){
  42. Workerpeter=newWorker();
  43. Bossboss=newBoss();
  44. peter.completed+=newWorkCompleted(boss.WorkCompleted);
  45. peter.started+=newWorkStarted(Universe.WorkerStartedWork);
  46. peter.completed+=newWorkCompleted(Universe.WorkerCompletedWork);
  47. peter.DoWork();
  48. Console.WriteLine("Main:workercompletedwork");
  49. Console.ReadLine();
  50. }
  51. }


Peter知道异步获取结果会带来一些问题。由于异步触发事件,所以目标方法有可能执行于另一个线程中,就像Peter的“目标方法何时完 成”的通知那样。然而,Peter熟悉第14章“多线程用户界面”,因此,他知道在构建WinForms应用程序时如何去处理此类问题。

从此,他们一直过得都很快乐。
分享到:
评论

相关推荐

    C#与.NET3.5高级程序设计(第4版) 中文1

    2.6 使用Visual C# 2008 Express构建.NET应用程序 38 2.7 使用Visual Studio 2008构建.NET应用程序 40 2.8 其他.NET开发工具 49 2.9 小结 50 第二部分 C#核心编程结构 第3章 C#核心编程结构Ⅰ 52 3.1 一...

    详解如何在C#/.NET Core中使用责任链模式

    他经常来询问我在实际业务应用中使用了哪些设计模式。单例模式、工厂模式、中介者模式 – 都是我之前使用过,甚至写过相关文章的模式。但是有一种模式是我还没有写过文章,即责任链模式。 什么是责任链?# 责任链...

    C#.Net的常见面试试题

    C#.Net的常见面试试题<br/><br/><br/>1.面向对象的思想主要包括什么?<br/><br/>2.什么是ASP.net中的用户控件<br/><br/>3.什么叫应用程序域?什么是受管制的代码?什么是强类型系统?什么是装箱和拆箱?什么是重载?...

    从Java的角度来讲解C#的代理、事件和事件句柄的原理

    当阅读到C#的代理、事件、事件句柄概念时,本人非常开心,因为《Programmers Heaven C# School》一书上说“事件/事件句柄”是基于观察者模式的,但是,看书上代码一点都不“模式”。所以,本人根据Java的监听器概念...

    asp.net知识库

    利用委托机制处理.NET中的异常 与正则表达式相关的几个小工具 你真的了解.NET中的String吗? .NET中的方法及其调用(一) 如何判断ArrayList,Hashtable,SortedList 这类对象是否相等 帮助解决网页和JS文件中的中文...

    C# 反射技术应用

     反射(Reflection)是.NET中的重要机制,通过放射,可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件,以及构造函数等。还可以获得每个成员的名称、限定符和...

    北京中科信软 Visual Basic.NET培训

    委托和事件 线程 反射 对象序列化 数据流和文件 Internet访问与网络编程 EventLog MSMQ Remoting 与COM的交互 Windows Service 三 ADO.NET 数据库连接与连接池控制 执行数据操作命令 DataReader ...

    [ASP.NET.AJAX编程参考手册(涵盖ASP.NET.3.5及2.0)].(美)霍斯拉维.扫描版.pdf

    书中的所有代码都通过了ASP.NET 2.0和ASP.NET 3.5的测试。通过本书,您将学习到这些框架之间是如何进行协同以满足AJAx应用需求的。本书将为您提供开发ASP.NET AJAX应用所必需的技能和知识。 内容简介 本书以AJAX为...

    网鸟Asp.Net模板引擎 v4.2

    为了增强组件的可扩展性和满足用户处理代码的常规需求,我们在组件内部提供了一系列的接口、委托、事件等,您可以通过这些接口、委托和事件来增强网鸟Asp.Net模板引擎的功能。 概览 项目名称:网鸟Asp.Net模板...

    .NET之美:.NET关键技术深入分析

    第3章C#中的委托和事件 3.1理解委托 3.1.1 将方法作为方法的参数 3.1.2将方法绑定到委托 3.1.3委托与接口 3.2事件的由来 3.2.1 更好的封装性 3.2.2 限制类型能力 3.3委托的编译代码 3.4.NET框架中的委托和...

    亮剑.NET深入体验与实战精要2

    本书既考虑到实际开发中经常遇到的困惑和难题,也分析了解决问题的思路和方法,更总结出项目开发中不可或缺的技术点及思想。读者可以在欣赏一个个有趣例子的过程中,不知不觉具备开发真正商业项目的能力。 本书集...

    亮剑.NET深入体验与实战精要3

    本书既考虑到实际开发中经常遇到的困惑和难题,也分析了解决问题的思路和方法,更总结出项目开发中不可或缺的技术点及思想。读者可以在欣赏一个个有趣例子的过程中,不知不觉具备开发真正商业项目的能力。 本书集...

    CLR.via.C#.(中文第3版)(自制详细书签)

    《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...

    ASP.NET 3.5 开发大全

    4.1.5 ASP.NET网站和ASP.NET应用程序的区别 4.2 代码隐藏页模型的解释过程 4.3 代码隐藏页模型的事件驱动处理 4.4 ASP.NET客户端状态 4.4.1 视图状态 4.4.2 控件状态 4.4.3 隐藏域 4.4.4 Cookie 4.4.5 客户端状态...

    CLR.via.C#.(中文第3版)(自制详细书签)Part2

    《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...

    ASP.NET 3.5 开发大全word课件

    4.1.5 ASP.NET网站和ASP.NET应用程序的区别 4.2 代码隐藏页模型的解释过程 4.3 代码隐藏页模型的事件驱动处理 4.4 ASP.NET客户端状态 4.4.1 视图状态 4.4.2 控件状态 4.4.3 隐藏域 4.4.4 Cookie 4.4.5 客户端状态...

    ASP.NET3.5从入门到精通

    4.1.5 ASP.NET 网站和ASP.NET 应用程序的区别 4.2 代码隐藏页模型的解释过程 4.3 代码隐藏页模型的事件驱动处理 4.4 ASP.NET 客户端状态 4.4.1 视图状态 4.4.2 控件状态 4.4.3 隐藏域 4.4.4 Cookie 4.4.5 客户端状态...

    ASP.NET 3.5 开发大全1-5

    4.1.5 ASP.NET网站和ASP.NET应用程序的区别 4.2 代码隐藏页模型的解释过程 4.3 代码隐藏页模型的事件驱动处理 4.4 ASP.NET客户端状态 4.4.1 视图状态 4.4.2 控件状态 4.4.3 隐藏域 4.4.4 Cookie 4.4.5 客户端状态...

    Visual C# 2010程序设计教程PPT

    C#Z中的继承机制 虚方法与override关键字 多态性 第9章 泛型编程 泛型的概念 泛型方法 泛型约束 使用泛型 第10章 windows应用程序开发 windows窗体开发基础 常用windows控件 ...

    C#实训教程

    14.3 事件和事件处理程序 290 14.4 事件参数 290 14.5 Windows 应用程序 290 14.6 窗体属性、方法和事件 291 14.7 this 关键字 292 14.8 控件概念 292 14.9 各种类型控件 292 14.10 控件的一些通用属性 294 ...

Global site tag (gtag.js) - Google Analytics