C#中的委托和事件续.docx

上传人:b****9 文档编号:26026292 上传时间:2023-06-17 格式:DOCX 页数:26 大小:29.56KB
下载 相关 举报
C#中的委托和事件续.docx_第1页
第1页 / 共26页
C#中的委托和事件续.docx_第2页
第2页 / 共26页
C#中的委托和事件续.docx_第3页
第3页 / 共26页
C#中的委托和事件续.docx_第4页
第4页 / 共26页
C#中的委托和事件续.docx_第5页
第5页 / 共26页
点击查看更多>>
下载资源
资源描述

C#中的委托和事件续.docx

《C#中的委托和事件续.docx》由会员分享,可在线阅读,更多相关《C#中的委托和事件续.docx(26页珍藏版)》请在冰豆网上搜索。

C#中的委托和事件续.docx

C#中的委托和事件续

引言

如果你看过了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(){

       return"Subscriber1";

   }

}

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声明为委托变量还是事件都无所谓了,因为它是私有的,即便将它声明为一个委托变量,客户端也看不到它,也就无法通过它来触发事件、调用订阅者的方法。

而只能通过Register()和UnRegister()方法来注册和取消注册,通过调用DoSomething()方法触发事件(而不是NumberChanged本身,这在前面已经讨论过了)。

2、我们还应该发现,这里采用的、对NumberChanged委托变量的访问模式和C#中的属性是多么类似啊?

大家知道,在C#中通常一个属性对应一个类型成员,而在类型的外部对成员的操作全部通过属性来完成。

尽管这里对委托变量的处理是类似的效果,但却使用了两个方法来进行模拟,有没有办法像使用属性一样来完成上面的例子呢?

答案是有的,C#中提供了一种叫事件访问器(EventAccessor)的东西,它用来封装委托变量。

如下面例子所示:

classProgram{

   staticvoidMain(string[]args){

       Publishserpub=newPublishser();

       Subscriber1sub1=newSubscriber1();

       Subscriber2sub2=newSubscriber2();

       pub.NumberChanged-=sub1.OnNumberChanged; //不会有任何反应

       pub.NumberChanged+=sub2.OnNumberChanged; //注册了sub2

       pub.NumberChanged+=sub1.OnNumberChanged; //sub1将sub2的覆盖掉了

       

       pub.DoSomething();         //触发事件

   }

}

//定义委托

publicdelegatestringGeneralEventHandler();

//定义事件发布者

publicclassPublishser{

   //声明一个委托变量

   privateGeneralEventHandlernumberChanged;

   //事件访问器的定义

   publiceventGeneralEventHandlerNumberChanged{

       add{

           numberChanged=value;

       }

       remove{

           numberChanged-=value;

       }

   }

   

   publicvoidDoSomething(){

       //做某些其他的事情

       if(numberChanged!

=null){   //通过委托变量触发事件

           stringrtn=numberChanged();

           Console.WriteLine("Return:

{0}",rtn);     //打印返回的字符串

       }

   }

}

//定义事件订阅者

publicclassSubscriber1{

   publicstringOnNumberChanged(){

       Console.WriteLine("Subscriber1Invoked!

");

       return"Subscriber1";

   }

}

publicclassSubscriber2{/*与上类同,略*/}

publicclassSubscriber3{/*与上类同,略*/}

上面代码中类似属性的publiceventGeneralEventHandlerNumberChanged{add{...}remove{...}}语句便是事件访问器。

使用了事件访问器以后,在DoSomething方法中便只能通过numberChanged委托变量来触发事件,而不能NumberChanged事件访问器(注意它们的大小写不同)触发,它只用于注册和取消注册。

下面是代码输出:

Subscriber1Invoked!

Return:

Subscriber1

获得多个返回值与异常处理

现在假设我们想要获得多个订阅者的返回值,以List的形式返回,该如何做呢?

我们应该记得委托定义在编译时会生成一个继承自MulticastDelegate的类,而这个MulticastDelegate又继承自Delegate,在Delegate内部,维护了一个委托链表,链表上的每一个元素,为一个只包含一个目标方法的委托对象。

而通过Delegate基类的GetInvocationList()静态方法,可以获得这个委托链表。

随后我们遍历这个链表,通过链表中的每个委托对象来调用方法,这样就可以分别获得每个方法的返回值:

classProgram4{

   staticvoidMain(string[]args){

       Publishserpub=newPublishser();

       Subscriber1sub1=newSubscriber1();

       Subscriber2sub2=newSubscriber2();

       Subscriber3sub3=newSubscriber3();

       pub.NumberChanged+=newDemoEventHandler(sub1.OnNumberChanged);

       pub.NumberChanged+=newDemoEventHandler(sub2.OnNumberChanged);

       pub.NumberChanged+=newDemoEventHandler(sub3.OnNumberChanged);

       Listlist=pub.DoSomething(); //调用方法,在方法内触发事件

       foreach(stringstrinlist){

           Console.WriteLine(str);

       }          

   }

}

publicdelegatestringDemoEventHandler(intnum);

//定义事件发布者

publicclassPublishser{

   publiceventDemoEventHandlerNumberChanged;   //声明一个事件

   publicListDoSomething(){

       //做某些其他的事

       ListstrList=newList();

       if(NumberChanged==null)returnstrList;

       //获得委托数组

       Delegate[]delArray=NumberChanged.GetInvocationList();

       foreach(DelegatedelindelArray){

           //进行一个向下转换

           DemoEventHandlermethod=(DemoEventHandler)del;

           strList.Add(method(100));      //调用方法并获取返回值

       }

       

       returnstrList;

   }

}

//定义事件订阅者

publicclassSubscriber1{

   publicstringOnNumberChanged(intnum){

       Console.WriteLine("Subscriber1invoked,number:

{0}",num);

       return"[Subscriber1returned]";

   }

}

publicclassSubscriber3{与上面类同,略}

publicclassSubscriber3{与上面类同,略}

如果运行上面的代码,可以得到这样的输出:

Subscriber1invoked,number:

100

Subscriber2invoked,number:

100

Subscriber3invoked,number:

100

[Subscriber1returned]

[Subscriber2returned]

[Subscriber3returned]

可见我们获得了三个方法的返回值。

而我们前面说过,很多情况下委托的定义都不包含返回值,所以上面介绍的方法似乎没有什么实际意义。

其实通过这种方式来触发事件最常见的情况应该是在异常处理中,因为很有可能在触发事件时,订阅者的方法会抛出异常,而这一异常会直接影响到发布者,使得发布者程序中止,而后面订阅者的方法将不会被执行。

因此我们需要加上异常处理,考虑下面一段程序:

classProgram5{

   staticvoidMain(string[]args){

       Publisherpub=newPublisher();

       Subscriber1sub1=newSubscriber1();

       Subscriber2sub2=newSubscriber2();

       Subscriber3sub3=newSubscriber3();

       pub.NumberChanged+=newDemoEventHandler(sub1.OnNumberChanged);

       pub.NumberChanged+=newDemoEventHandler(sub2.OnNumberChanged);

       pub.NumberChanged+=newDemoEventHandler(sub3.OnNumberChanged);

   }

}

publicclassPublisher{

   publiceventEventHandlerMyEvent;

   publicvoidDoSomething(){

       //做某些其他的事情

       if(MyEvent!

=null){

           try{

               MyEvent(this,EventArgs.Empty);

           }catch(Exceptione){

               Console.WriteLine("Exception:

{0}",e.Message);

           }

       }

   }

}

publicclassSubscriber1{

   publicvoidOnEvent(objectsender,EventArgse){

       Console.WriteLine("Subscriber1Invoked!

");

   }

}

publicclassSubscriber2{

   publicvoidOnEvent(objectsender,EventArgse){

       thrownewException("Subscriber2Failed");

   }

}

publicclassSubscriber3{/*与Subsciber1类同,略*/}

注意到我

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 其它课程

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1