漫谈C#开发中的事件与委托机制

概述

C#中的委托类似于C或C++中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的。

C#中的“事件”是当对象发生某些事情时,类向该类的客户提供通知的一种方法。事件最常见的用途是用于图形用户界面;通常,表示界面中的控件的类具有一些事件,当用户对控件进行某些操作(如单击某个按钮)时,将通知这些事件。

使用委托来声明事件。委托对象封装一个方法,以便可以匿名调用该方法。事件是类允许客户为其提供方法(事件发生时应调用这些方法)的委托的一种方法。事件发生时,将调用其客户提供给它的委托。

注明:委托是对方法的包装 在不确定要调用什么方法时候而又不能用抽象或者多态实现的时候用委托。

 
  1. publicinterfacePilotLamp
  2. {
  3. ///<summary>
  4. ///greenlight
  5. ///</summary>
  6. voidTurnOn();
  7. ///<summary>
  8. ///notice
  9. ///</summary>
  10. stringNotice
  11. {
  12. get;
  13. set;
  14. }
  15. }

先创建PilotLamp.cs

再创建DelegateEvent.cs:

 
  1. publicdelegatevoidEventHandler();

再创建TrafficLight.cs:

 
  1. publicclassTrafficLight:PilotLamp
  2. {
  3. publiceventEventHandlerNotices;
  4. privatestringnotice;
  5. #regionGreenLight成员
  6. publicvoidTurnOn()
  7. {
  8. if(Notices!=null)
  9. Notices();
  10. }
  11. publicstringNotice
  12. {
  13. get
  14. {
  15. returnnotice;
  16. }
  17. set
  18. {
  19. notice=value;
  20. }
  21. }
  22. #endregion
  23. }

再创建Driver.cs

 
  1. publicclassDriver
  2. privatestringName;
  3. privatePilotLampgreenLight;
  4. publicDriver(stringname,PilotLampgreenLight)
  5. {
  6. this.Name=name;
  7. this.greenLight=greenLight;
  8. }
  9. publicvoidGoLeft()
  10. {
  11. Console.WriteLine(string.Format("{1}司机,{0},请向左开车.",greenLight.Notice,Name));
  12. }
  13. }

再创建Pedestrian.cs:

 
  1. publicclassPedestrian
  2. {
  3. privatestringName;
  4. privatePilotLampgreenLight;
  5. publicPedestrian(stringname,PilotLampgreenLight)
  6. {
  7. this.Name=name;
  8. this.greenLight=greenLight;
  9. }
  10. publicvoidGoThrough()
  11. {
  12. Console.WriteLine(string.Format("{0}同志,{1},请向前走.",Name,greenLight.Notice));
  13. }
  14. }

最后再调用:

 
  1. publicpartialclassRun:Form
  2. {
  3. publicRun()
  4. {
  5. InitializeComponent();
  6. }
  7. privatevoidbtnRun_Click(objectsender,EventArgse)
  8. {
  9. //-------------------------------------
  10. TrafficLighttrafficLight=newTrafficLight();
  11. DriverdriverOne=newDriver("张三",trafficLight);
  12. DriverdriverTwo=newDriver("李四",trafficLight);
  13. PedestrianpedestrianOne=newPedestrian("王五",trafficLight);
  14. PedestrianpedestrianTwo=newPedestrian("麻六",trafficLight);
  15. trafficLight.Notices+=newObserver.EventHandler(driverOne.GoLeft);
  16. trafficLight.Notices+=newObserver.EventHandler(driverTwo.GoLeft);
  17. trafficLight.Notices+=newObserver.EventHandler(pedestrianOne.GoThrough);
  18. trafficLight.Notices+=newObserver.EventHandler(pedestrianTwo.GoThrough);
  19. trafficLight.Notice="绿灯亮了.";
  20. trafficLight.TurnOn();
  21. //-------------------------------------
  22. }
  23. }

输出时选控制台应用程序如图:

image

结果如下图:

image

事件的使用示例:

 
  1. namespaceDelegateAndEvent
  2. {
  3. classProgram
  4. {
  5. staticvoidMain(string[]args)
  6. {
  7. Publishserpub=newPublishser();
  8. OneScriberoneSub=newOneScriber();
  9. TwoScribertwoSub=newTwoScriber();
  10. ThreeScriberthreeSub=newThreeScriber();
  11. pub.NumberChanged+=newGeneralEventHandler(oneSub.OnNumberChanged);
  12. pub.NumberChanged+=newGeneralEventHandler(twoSub.OnNumberChanged);
  13. pub.NumberChanged+=newGeneralEventHandler(threeSub.OnNumberChanged);
  14. pub.DoSomething();
  15. }
  16. }
  17. publicdelegatestringGeneralEventHandler();
  18. publicclassPublishser
  19. {
  20. publiceventGeneralEventHandlerNumberChanged;
  21. publicvoidDoSomething()
  22. {
  23. if(NumberChanged!=null)
  24. {
  25. Delegate[]generalEventHandlers=NumberChanged.GetInvocationList();
  26. foreach(DelegategeneralEventHandleringeneralEventHandlers)
  27. {
  28. GeneralEventHandlermothed=(GeneralEventHandler)generalEventHandler;
  29. stringrtn=mothed();
  30. Console.WriteLine(rtn);
  31. System.Threading.Thread.Sleep(2000);
  32. }
  33. }
  34. }
  35. }
  36. publicclassOneScriber
  37. {
  38. publicstringOnNumberChanged()
  39. {
  40. return"OneSubscriber";
  41. }
  42. }
  43. publicclassTwoScriber
  44. {
  45. publicstringOnNumberChanged()
  46. {
  47. return"TwoSubscriber";
  48. }
  49. }
  50. publicclassThreeScriber
  51. {
  52. publicstringOnNumberChanged()
  53. {
  54. return"ThreeSubscriber";
  55. }
  56. }
  57. }

运行结果:

image

注意到Delegate是GeneralEventHandler的基类,所以为了触发事件,先要进行一个向下的强制转换,之后才能在其上触发事件,调用所有注册对象的方法。除了使用这种方式以外,还有一种更灵活方式可以调用方法,它是定义在Delegate基类中的DynamicInvoke()方法:

 
  1. publicobjectDynamicInvoke(paramsobject[]args);

这可能是调用委托最通用的方法了,适用于所有类型的委托。它接受的参数为object[],也就是说它可以将任意数量的任意类型作为参数,并返回单个object对象。上面的DoSomething()方法也可以改写成下面这种通用形式:

代码作如下改动:

 
  1. namespaceDelegateAndEvent
  2. {
  3. classProgram
  4. {
  5. staticvoidMain(string[]args)
  6. {
  7. Publishserpub=newPublishser();
  8. OneScriberoneSub=newOneScriber();
  9. TwoScribertwoSub=newTwoScriber();
  10. ThreeScriberthreeSub=newThreeScriber();
  11. pub.NumberChanged+=newGeneralEventHandler(oneSub.OnNumberChanged);
  12. pub.NumberChanged+=newGeneralEventHandler(twoSub.OnNumberChanged);
  13. pub.NumberChanged+=newGeneralEventHandler(threeSub.OnNumberChanged);
  14. List<string>strlist=pub.DoSomething();
  15. foreach(stringresultinstrlist)
  16. Console.WriteLine(result);
  17. System.Threading.Thread.Sleep(5000);
  18. }
  19. }
  20. publicdelegatestringGeneralEventHandler();
  21. publicclassPublishser
  22. {
  23. publiceventGeneralEventHandlerNumberChanged;
  24. publicList<string>DoSomething()
  25. {
  26. List<string>strList=newList<string>();
  27. if(NumberChanged==null)returnstrList;
  28. Delegate[]generalEventHandlers=NumberChanged.GetInvocationList();
  29. foreach(DelegategeneralEventHandleringeneralEventHandlers)
  30. {
  31. //GeneralEventHandlermothed=(GeneralEventHandler)generalEventHandler;
  32. stringrtn=generalEventHandler.DynamicInvoke(null).ToString();
  33. strList.Add(rtn);
  34. }
  35. returnstrList;
  36. }
  37. }
  38. publicclassOneScriber
  39. {
  40. publicstringOnNumberChanged()
  41. {
  42. return"OneSubscriber";
  43. }
  44. }
  45. publicclassTwoScriber
  46. {
  47. publicstringOnNumberChanged()
  48. {
  49. return"TwoSubscriber";
  50. }
  51. }
  52. publicclassThreeScriber
  53. {
  54. publicstringOnNumberChanged()
  55. {
  56. return"ThreeSubscriber";
  57. }
  58. }
  59. }

结果如下:

image

还是一样的结果.

委托的定义会生成继承自MulticastDelegate的完整的类,其中包含Invoke()、BeginInvoke()和EndInvoke()方法。当我们直接调用委托时,实际上是调用了Invoke()方法,它会中断调用它的客户端,然后在客户端线程上执行所有订阅者的方法(客户端无法继续执行后面代码),最后将控制权返回客户端。注意到BeginInvoke()、EndInvoke()方法,在.Net中,异步执行的方法通常都会配对出现,并且以Begin和End作为方法的开头(最常见的可能就是Stream类的BeginRead()和EndRead()方法了)。它们用于方法的异步执行,即是在调用BeginInvoke()之后,客户端从线程池中抓取一个闲置线程,然后交由这个线程去执行订阅者的方法,而客户端线程则可以继续执行下面的代码。

BeginInvoke()接受“动态”的参数个数和类型,为什么说“动态”的呢?因为它的参数是在编译时根据委托的定义动态生成的,其中前面参数的个数和类型与委托定义中接受的参数个数和类型相同,最后两个参数分别是AsyncCallback和Object类型,对于它们更具体的内容,可以参见下一节委托和方法的异步调用部分。现在,我们仅需要对这两个参数传入null就可以了。另外还需要注意几点:

在委托类型上调用BeginInvoke()时,此委托对象只能包含一个目标方法,所以对于多个订阅者注册的情况,必须使用GetInvocationList()获得所有委托对象,然后遍历它们,分别在其上调用BeginInvoke()方法。如果直接在委托上调用BeginInvoke(),会抛出异常,提示“委托只能包含一个目标方法”。

如果订阅者的方法抛出异常,.NET会捕捉到它,但是只有在调用EndInvoke()的时候,才会将异常重新抛出。而在本例中,我们不使用EndInvoke()(因为我们不关心订阅者的执行情况),所以我们无需处理异常,因为即使抛出异常,也是在另一个线程上,不会影响到客户端线程(客户端甚至不知道订阅者发生了异常,这有时是好事有时是坏事)

BeginInvoke()方法属于委托定义所生成的类,它既不属于MulticastDelegate也不属于Delegate基类, 我们需要进行一个向下转换,来获取到实际的委托类型。

示例:

 
  1. namespaceDelegateAndEvent
  2. {
  3. classProgram
  4. {
  5. staticvoidMain(string[]args)
  6. {
  7. Publishserpub=newPublishser();
  8. OneScriberoneSub=newOneScriber();
  9. TwoScribertwoSub=newTwoScriber();
  10. ThreeScriberthreeSub=newThreeScriber();
  11. pub.NumberChanged+=newGeneralEventHandler(oneSub.OnNumberChanged);
  12. pub.NumberChanged+=newGeneralEventHandler(twoSub.OnNumberChanged);
  13. pub.NumberChanged+=newGeneralEventHandler(threeSub.OnNumberChanged);
  14. List<string>strlist=pub.DoSomething();
  15. foreach(stringresultinstrlist)
  16. Console.WriteLine(result);
  17. System.Threading.Thread.Sleep(5000);
  18. }
  19. }
  20. publicdelegatestringGeneralEventHandler(objectsender,EventArgse);
  21. publicclassPublishser
  22. {
  23. publiceventGeneralEventHandlerNumberChanged;
  24. publicList<string>DoSomething()
  25. {
  26. List<string>strList=newList<string>();
  27. if(NumberChanged==null)returnstrList;
  28. Delegate[]generalEventHandlers=NumberChanged.GetInvocationList();
  29. foreach(DelegategeneralEventHandleringeneralEventHandlers)
  30. {
  31. GeneralEventHandlermothed=(GeneralEventHandler)generalEventHandler;
  32. IAsyncResultresult=mothed.BeginInvoke(this,EventArgs.Empty,null,null);
  33. stringstr=mothed.EndInvoke(result);
  34. strList.Add(str);
  35. }
  36. returnstrList;
  37. }
  38. }
  39. publicclassOneScriber
  40. {
  41. publicstringOnNumberChanged(objectsender,EventArgse)
  42. {
  43. return"OneSubscriber";
  44. }
  45. }
  46. publicclassTwoScriber
  47. {
  48. publicstringOnNumberChanged(objectsender,EventArgse)
  49. {
  50. return"TwoSubscriber";
  51. }
  52. }
  53. publicclassThreeScriber
  54. {
  55. publicstringOnNumberChanged(objectsender,EventArgse)
  56. {
  57. return"ThreeSubscriber";
  58. }
  59. }
  60. }

结果:

image

BeginInvoke的另外两个参数分别是AsyncCallback和Object类型,其中AsyncCallback是一个委托类型,它用于方法的回调,即是说当异步方法执行完毕时自动进行调用的方法。它的定义为:

public delegate void AsyncCallback(IAsyncResult ar);

Object类型用于传递任何你想要的数值,它可以通过IAsyncResult的AsyncState属性获得。

http://www.cnblogs.com/springyangwc/archive/2011/06/20/2085541.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值