您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 信息化管理 > C#中的委托和事件(续)
C#中的委托和事件(续)Delegates&EventsinC#(continued)张子阳@163.comC#中的委托和事件(续)...........................................................................................................................1引言...................................................................................................................................................3为什么要使用事件而不是委托变量?...........................................................................................3为什么委托定义的返回值通常都为void?...................................................................................4如何让事件只允许一个客户订阅?...............................................................................................5获得多个返回值与异常处理...........................................................................................................8委托中订阅者方法超时的处理.....................................................................................................12委托和方法的异步调用.................................................................................................................17总结.................................................................................................................................................22引言如果你看过了C#中的委托和事件一文,我想你对委托和事件已经有了一个基本的认识。但那些远不是委托和事件的全部内容,还有很多的地方没有涉及。本文将讨论委托和事件一些更为细节的问题,包括一些大家常问到的问题,以及事件访问器、异常处理、超时处理和异步方法调用等内容。为什么要使用事件而不是委托变量?在C#中的委托和事件中,我提出了两个为什么在类型中使用事件向外部提供方法注册,而不是直接使用委托变量的原因。主要是从封装性和易用性上去考虑,但是还漏掉了一点,事件应该由事件发布者触发,而不应该由客户端(客户程序)来触发。这句话是什么意思呢?请看下面的范例:NOTE:注意这里术语的变化,当我们单独谈论事件,我们说发布者(publisher)、订阅者(subscriber)、客户端(client)。当我们讨论Observer模式,我们说主题(subject)和观察者(observer)。客户端通常是包含Main()方法的Program类。classProgram{staticvoidMain(string[]args){Publishserpub=newPublishser();Subscribersub=newSubscriber();pub.NumberChanged+=newNumberChangedEventHandler(sub.OnNumberChanged);pub.DoSomething();//应该通过DoSomething()来触发事件pub.NumberChanged(100);//但可以被这样直接调用,对委托变量的不恰当使用}}//定义委托publicdelegatevoidNumberChangedEventHandler(intcount);//定义事件发布者publicclassPublishser{privateintcount;publicNumberChangedEventHandlerNumberChanged;//声明委托变量//publiceventNumberChangedEventHandlerNumberChanged;//声明一个事件publicvoidDoSomething(){//在这里完成一些工作...if(NumberChanged!=null){//触发事件count++;NumberChanged(count);}}}//定义事件订阅者publicclassSubscriber{publicvoidOnNumberChanged(intcount){Console.WriteLine(Subscribernotified:count={0},count);}}上面代码定义了一个NumberChangedEventHandler委托,然后我们创建了事件的发布者Publisher和订阅者Subscriber。当使用委托变量时,客户端可以直接通过委托变量触发事件,也就是直接调用pub.NumberChanged(100),这将会影响到所有注册了该委托的订阅者。而事件的本意应该为在事件发布者在其本身的某个行为中触发,比如说在方法DoSomething()中满足某个条件后触发。通过添加event关键字来发布事件,事件发布者的封装性会更好,事件仅仅是供其他类型订阅,而客户端不能直接触发事件(语句pub.NumberChanged(100)无法通过编译),事件只能在事件发布者Publisher类的内部触发(比如在方法pub.DoSomething()中),换言之,就是NumberChanged(100)语句只能在Publisher内部被调用。大家可以尝试一下,将委托变量的声明那行代码注释掉,然后取消下面事件声明的注释。此时程序是无法编译的,当你使用了event关键字之后,直接在客户端触发事件这种行为,也就是直接调用pub.NumberChanged(100),是被禁止的。事件只能通过调用DoSomething()来触发。这样才是事件的本意,事件发布者的封装才会更好。就好像如果我们要定义一个数字类型,我们会使用int而不是使用object一样,给予对象过多的能力并不见得是一件好事,应该是越合适越好。尽管直接使用委托变量通常不会有什么问题,但它给了客户端不应具有的能力,而使用事件,可以限制这一能力,更精确地对类型进行封装。NOTE:这里还有一个约定俗称的规定,就是订阅事件的方法的命名,通常为“On事件名”,比如这里的OnNumberChanged。为什么委托定义的返回值通常都为void?尽管并非必需,但是我们发现很多的委托定义返回值都为void,为什么呢?这是因为委托变量可以供多个订阅者注册,如果定义了返回值,那么多个订阅者的方法都会向发布者返回数值,结果就是后面一个返回的方法值将前面的返回值覆盖掉了,因此,实际上只能获得昀后一个方法调用的返回值。可以运行下面的代码测试一下。除此以外,发布者和订阅者是松耦合的,发布者根本不关心谁订阅了它的事件、为什么要订阅,更别说订阅者的返回值了,所以返回订阅者的方法返回值大多数情况下根本没有必要。classProgram{staticvoidMain(string[]args){Publishserpub=newPublishser();Subscriber1sub1=newSubscriber1();Subscriber2sub2=newSubscriber2();Subscriber3sub3=newSubscriber3();pub.NumberChanged+=newGeneralEventHandler(sub1.OnNumberChanged);pub.NumberChanged+=newGeneralEventHandler(sub2.OnNumberChanged);pub.NumberChanged+=newGeneralEventHandler(sub3.OnNumberChanged);pub.DoSomething();//触发事件}}//定义委托publicdelegatestringGeneralEventHandler();//定义事件发布者publicclassPublishser{publiceventGeneralEventHandlerNumberChanged;//声明一个事件publicvoidDoSomething(){if(NumberChanged!=null){//触发事件stringrtn=NumberChanged();Console.WriteLine(rtn);//打印返回的字符串,输出为Subscriber3}}}//定义事件订阅者publicclassSubscriber1{publicstringOnNumberChanged(){returnSubscriber1;}}publicclassSubscriber2{/*略,与上类似,返回Subscriber2*/}publicclassSubscriber3{/*略,与上类似,返回Subscriber3*/}如果运行这段代码,得到的输出是Subscriber3,可以看到,只得到了昀后一个注册方法的返回值。如何让事件只允许一个客户订阅?少数情况下,比如像上面,为了避免发生“值覆盖”的情况(更多是在异步调用方法时,后面会讨论),我们可能想限制只允许一个客户端注册。此时怎么做呢?我们可以向下面这样,将事件声明为private的,然后提供两个方法来进行注册和取消注册://定义事件发布者publicclassPublishser{privateeventGeneralEventHandlerNumberChanged;//声明一个私有事件//注册事件publicvoidRegister(GeneralEventHandlermethod){NumberChanged=method;}//取消注册publicvoidUnRegister(GeneralEventHandlermethod){NumberChanged-=method;}publicvoidDoSomething(){//做某些其余的事情if(NumberChanged!=null){//触发事件stringrtn=NumberChanged();Console.WriteLine(Return:{0},rtn);//打印返回的字符串,输出为Subscriber3}}}NOTE:注意上面,在UnRegister()中,没有进行任何判断就使用了NumberChanged-=method语句。这是因为即使method方法没有进行过注册,此行语句也不会有任何问题,不会抛出异常,仅仅是不会产生任何效果而已。注意在Register()方法中,我们使用了赋值操作符“=”,而非“+=”,通过这种方式就避免了多个方法注册。上面的代码尽管可以完成我们的需要,但是此时大家还应该注意下面两点:1、将NumberChanged声明为委托变量还是事件都无所谓了,因为它是私有的,即便将它声明为一个委托变量,客户端也看不到它,也就无法通过它来触发事件、调用订
本文标题:C#中的委托和事件(续)
链接地址:https://www.777doc.com/doc-4232228 .html