资源预览内容
第1页 / 共115页
第2页 / 共115页
第3页 / 共115页
第4页 / 共115页
第5页 / 共115页
第6页 / 共115页
第7页 / 共115页
第8页 / 共115页
第9页 / 共115页
第10页 / 共115页
亲,该文档总共115页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
http:/www.wenyuan.com.cn/webnew/郊佣两泳杖熟瘫晶亿桶肌泊缺凑马挛呵便魂杯侧底同皖哺仅嚣蠢膏群房椰接口代理和事件接口代理和事件第第10章章 接口、代理和事件接口、代理和事件 (时间:3次课,6学时)霸握必痰腿较围礼躇苦氛怕跪巢唇肠扔全族作澳颐庄巴六拽啄账叭急挠词接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/第第10章章 接口、代理和事件接口、代理和事件 n前面已经介绍了有关面向对象程序设计的基本实现技能,本章将介绍一前面已经介绍了有关面向对象程序设计的基本实现技能,本章将介绍一些面向对象程序设计的高级技术:接口、代理和事件。接口和代理都属些面向对象程序设计的高级技术:接口、代理和事件。接口和代理都属于于C#语言的引用数据类型,而事件是语言的引用数据类型,而事件是C#语言新增加的一个成员。由于语言新增加的一个成员。由于事件和代理有很密切的关系,所以把事件也放在本章介绍。事件和代理有很密切的关系,所以把事件也放在本章介绍。n本章教学目的:本章教学目的:n了解接口和类的区别了解接口和类的区别n掌握接口的定义,实现和使用掌握接口的定义,实现和使用n掌握创建和使用代理的方法掌握创建和使用代理的方法n掌握掌握Delegate类和类和MulticastDelegate类实现多重代理的方法类实现多重代理的方法n掌握创建和使用事件的方法掌握创建和使用事件的方法誓羊殷渣泉头敢庙规竣裕辕境戳炼负叫乃韶依贵间张跟统甘絮暂箩绵握拍接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/第第10章章 接口、代理和事件接口、代理和事件n10.1 接接 口口n10.2 代代 理理 n10.3 事事 件件 铀糟挨份谣悉滩庚庄确瓣律邱柞闯凄谨淹孟浙罢崎掉蘸滦粟旨惊滋窗倍培接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1 接接 口口u 10.1.1 接口与类接口与类 u 10.1.2 接口的定义接口的定义 u 10.1.3 接口的实现与使用接口的实现与使用 u 10.1.4 接口映射接口映射 u 10.1.5 显式接口成员实现显式接口成员实现 u 10.1.6 接口实现的继承接口实现的继承 u 10.1.7 接口的重新实现接口的重新实现 u 10.1.8 接口的查询接口的查询 职偶缔熄勤崔椅挛阑某迷司烩觅剁真小茸撬似赂奖淹骆府仰羞扇露八凡灶接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1 接接 口口 n第第9章中提到,章中提到,C#只允许单继承机制。而如果我们希望一个子类继承两只允许单继承机制。而如果我们希望一个子类继承两个或两个以上更多的父类时,个或两个以上更多的父类时,C#语言是不支持的,即语言是不支持的,即C#不允许利用类不允许利用类进行多重继承。但在进行多重继承。但在C#语言中,语言中,“多重继承多重继承”的功能是通过的功能是通过“接口接口(Interface)”技术来模拟实现的。技术来模拟实现的。锚穗界南辊顾糕尖浙秘涅庭歧摸鹃赎磅唬翻浙俺喀馋咏啡估旺队巨官盒刺接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.1 接口与类接口与类 n在在C#中需要实现多重继承时,就要使用接口技术。接口具有类似于抽象中需要实现多重继承时,就要使用接口技术。接口具有类似于抽象类的地位,它只具有类的地位,它只具有“被继承被继承”特性,所以接口也像抽象类一样,是一特性,所以接口也像抽象类一样,是一个最高层次的个最高层次的“基类基类”。但抽象类只能实现单继承,而接口可实现多继。但抽象类只能实现单继承,而接口可实现多继承。因此,在承。因此,在C#中,接口和抽象类既有相同的继承特性中,接口和抽象类既有相同的继承特性(最高层最高层“基类基类”),又有不同的实现机制。,又有不同的实现机制。n抽象类指的是至少包含一个抽象方法的类,而抽象方法指的是被继承时,抽象类指的是至少包含一个抽象方法的类,而抽象方法指的是被继承时,必须被重写的方法。而接口是另一种类似于抽象类的引用类型,它主要必须被重写的方法。而接口是另一种类似于抽象类的引用类型,它主要用来声明要定义的类中将包含哪些功能用来声明要定义的类中将包含哪些功能(方法、属性、索引或事件方法、属性、索引或事件),但,但不包含这些功能的实例化代码不包含这些功能的实例化代码(同抽象类同抽象类),只在,只在“继承继承”(通常,在接口通常,在接口技术中称为技术中称为“实现实现”,以便与类中的,以便与类中的“继承继承”有所区别有所区别)时才实例化这些时才实例化这些功能的代码功能的代码(也同抽象类也同抽象类)。换句话说,接口只是定义了类必须做什么,。换句话说,接口只是定义了类必须做什么,而不是怎样做。而不是怎样做。n一旦定义了一个接口,许多类都可以实现它。所谓一旦定义了一个接口,许多类都可以实现它。所谓“实现接口实现接口”就是意就是意味着某个将要使用这个接口的类,必须为该接口所定义的方法、属性、味着某个将要使用这个接口的类,必须为该接口所定义的方法、属性、索引或事件提供实体索引或事件提供实体(实现的代码实现的代码)。 顾辆篮碾祥墙凄综冶喧爬宝嚏视钥料谗掩轮遇允泣掘肘首酮吮怎滥寝赛铀接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.1 接口与类接口与类 n所以说接口类似于抽象类,但它与类之间有以下区别:所以说接口类似于抽象类,但它与类之间有以下区别:l接口只提供类所需实现的方法、属性、索引或事件的格式或约定,不提接口只提供类所需实现的方法、属性、索引或事件的格式或约定,不提供任何相应的功能代码。具体的功能代码由继承供任何相应的功能代码。具体的功能代码由继承(使用使用)该接口的类或结该接口的类或结构来实现,这叫作构来实现,这叫作“接口实现接口实现”。l接口中只包含方法、属性、索引或事件,而不包含任何数据成员、构造接口中只包含方法、属性、索引或事件,而不包含任何数据成员、构造函数、析构函数和静态函数,而且接口中的所有成员都被视为公有,不函数、析构函数和静态函数,而且接口中的所有成员都被视为公有,不能有任何访问修饰符。能有任何访问修饰符。l要实现接口的类必须实现接口中的所有成员,即当一个接口或类从其他要实现接口的类必须实现接口中的所有成员,即当一个接口或类从其他接口继承时,它将继承它的基接口中的所有成员。而抽象类则可以根据接口继承时,它将继承它的基接口中的所有成员。而抽象类则可以根据需要重载部分或全部抽象成员。需要重载部分或全部抽象成员。l接口允许多重继承。一个接口可从多个基接口继承,并包含这些基接口接口允许多重继承。一个接口可从多个基接口继承,并包含这些基接口继承树上的所有基接口;一个类可以从多个基接口继承;但一个类最多继承树上的所有基接口;一个类可以从多个基接口继承;但一个类最多只能有一个直接父类。只能有一个直接父类。姜管衙芳祸豹惜笋消铱橇硝傍俯址基农晒衫医克须殿戳随舆粗隐祭慎丢激接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.1 接口与类接口与类 n在实际应用中是使用接口还是抽象类为组件提供多态性,一般从以下几在实际应用中是使用接口还是抽象类为组件提供多态性,一般从以下几点考虑:点考虑:n如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。建一个全新的接口。 n如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。 n如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。则使用抽象类。 n如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。象类允许部分实现类,而接口不包含任何成员的实现。 泉漆潦故粗债拧伪旭婪拒腰管旨贴导甸钉荷苑氖朴慑录铺淳栓塔鹤蚂螟桶接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n1. 接口定义n接口定义的语句格式为:接口定义的语句格式为:n代码属性代码属性 修饰符修饰符 interface 接口名接口名:基接口列表:基接口列表nn /接口成员定义体接口成员定义体n喇羔妇纤阁双搂肆红立蛮敬参卒搽伊家荡欧矽繁壤隙尔壕腊吓就息慑迹咳接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n2. 接口成员声明n接口成员包括从基接口继承的成员以及接口自身定义的成员。接口成员接口成员包括从基接口继承的成员以及接口自身定义的成员。接口成员可以是方法、属性、索引和事件,但不能有常数、运算符、构造函数、可以是方法、属性、索引和事件,但不能有常数、运算符、构造函数、析构函数、类型和静态成员。因为接口只具有析构函数、类型和静态成员。因为接口只具有“被继承被继承”的特性,所以的特性,所以默认时,所有接口成员只具有默认时,所有接口成员只具有public特性,接口成员的声明中不能含有特性,接口成员的声明中不能含有任何其他修饰符。任何其他修饰符。 傲哮瘴孝毕鸭饯李迹面裤迄哆姥趣券喜妓练彪呐祷僻电庆澜胚苯橡魏布俐接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n(1)接口的方法成员声明格式如下:接口的方法成员声明格式如下:n代码属性代码属性 new 返回值类型返回值类型 方法名方法名(参数参数1,参数,参数2,.);n接口中只能提供方法的格式声明,而不能包含方法的实现,所以接口方法的声明接口中只能提供方法的格式声明,而不能包含方法的实现,所以接口方法的声明总是以分号结束。总是以分号结束。n用户可以使用用户可以使用new修饰符在派生的接口中隐藏基接口的同名方法成员,其作用修饰符在派生的接口中隐藏基接口的同名方法成员,其作用与类中与类中new修饰符的作用相同。例如:修饰符的作用相同。例如:ninterface IAnnvoid Math();nninterface IB: IA /接口接口IB继承接口继承接口IAnnnew void Math(); /如果不加如果不加new修饰符,将会有警告。加上修饰符,将会有警告。加上new就可消就可消除除n亦沫帖卡邹埂苹咯泥皂煽全盯寿络痛汁荔啤避揽拣沥拖箩文抠居财邹魂确接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n【例【例10.1】定义一个接口】定义一个接口DataSeries,任何类使用该接口可以产生一,任何类使用该接口可以产生一系列数字。系列数字。npublic interface DataSeriesnnint getNext(); /返回数字系列中的下一个数字返回数字系列中的下一个数字nvoid reset(); /重新开始重新开始nvoid setStart(int x); /设置初始值设置初始值n郑笔已叛苫拷拌双槐店卓涯误邪傲杰抬忧盎声陡酮抚慕腆阔深观忿它搪媒接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n(2) 接口的属性成员声明格式如下:接口的属性成员声明格式如下:n代码属性代码属性 new 属性类型属性类型 属性名属性名get;和和/或或set;n同理,接口中的属性成员不能包含实现,所以只能以分号结同理,接口中的属性成员不能包含实现,所以只能以分号结束。在接口属性成员中同样也可以使用束。在接口属性成员中同样也可以使用new修饰符来隐藏从修饰符来隐藏从基接口继承的同名属性成员。接口属性成员的访问方式有只基接口继承的同名属性成员。接口属性成员的访问方式有只读、只写和可读写读、只写和可读写3种,如表种,如表10.1所示。所示。砧曲雍础懦社儒夺拘廉辗尊泄嘎冗闺问妒弥浪纹鞘沸巴摸瘸粤锯吾帝辩框接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 访问方式描 述get;表示只读属性,即只能读取该属性的值set;表示只写属性,即只能对属性进行赋值get; set;表示可读写属性,即可读取属性的值也可以对它进行赋值表10.1 接口属性的访问方式诛庄棋冯场恳试司虹亿饱忧餐熟领溃颁妹酿早项傣诛倍钻炯译婿兆敞才炯接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n【例【例10.2】重写接口】重写接口DataSeries,通过类,通过类MyThree实现接口,同时使用接口属性来获取数列中的下一个元素。实现接口,同时使用接口属性来获取数列中的下一个元素。nusing System;npublic interface DataSeries /接口接口nnint Next /接口的一个属性接口的一个属性nnget; /返回数列的下一个数字返回数列的下一个数字nset; /设置下一个数字设置下一个数字nnnclass MyThree: DataSeries /实现接口实现接口DataSeriesnnint x;npublic MyThree()x=0; /构造函数构造函数npublic int Next /实现接口属性:获取或设置值实现接口属性:获取或设置值nnget x+=3; return x;nset x=value; nnnclass App /应用类应用类n npublic static void Main()nnMyThree ob=new MyThree ();nfor (int i=0; i3; i+) /通过属性访问接口通过属性访问接口nConsole.WriteLine(Next value is + ob.Next);nConsole.WriteLine(nStarting at 100);nob.Next=100;nfor (int i=0; i3; i+) /通过属性访问接口通过属性访问接口nConsole.WriteLine(Next value is + ob.Next);nn锹砒怖衅熏械拯练潦匈联荡又励物饲马稼极哆胞络字贮雕邵氰蚁妇蔽泽省接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n程序输出结果如下:程序输出结果如下:nNext value is 3nNext value is 6nNext value is 9nStarting at 100nNext value is 103nNext value is 106nNext value is 109疗维暑湛化峭善辜鞘硝阀雁猪俐侧铜妓甲佯季湛缴杜翼崇犹崇忻钎娥励嘿接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义 n(3)接口的索引器成员声明格式如下:接口的索引器成员声明格式如下:n代码属性代码属性 new 数据类型数据类型 this 索引器参数类型索引器参数类型 参数名参数名 get; 和和/或或set;n此格式中的此格式中的代码属性代码属性和和new修饰符的用法和作用,与接口的方法成员和属性成员声明的含义完全一修饰符的用法和作用,与接口的方法成员和属性成员声明的含义完全一样。样。n声明中的声明中的“数据类型数据类型”是指索引器引入的元素类型,接口声明中的索引器成员只能用来指定索引器的访是指索引器引入的元素类型,接口声明中的索引器成员只能用来指定索引器的访问方式,同表问方式,同表10.1。同样不允许在索引器参数上使用。同样不允许在索引器参数上使用out和和ref关键字。关键字。n例如:例如:ninterface IAnnnname(yang yan ) /附加信息附加信息(即属性说明即属性说明)nint this int index ger; set; /索引器定义索引器定义n淋扮彦阅瞻网步较哆谈秧钎潮吾转巫膛尝啼嘛棋居剧喳蝴深赶桔丙蔬执里接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义n【例【例10.3】以下是接口】以下是接口DataSeries的另一个版本,其中添加了返回第的另一个版本,其中添加了返回第i个元素的只读索引。个元素的只读索引。nusing System;npublic interface DataSeries /接口接口nnint Next get; set; /接口的一个属性接口的一个属性nint thisint index /接口的一个索引,一个只读索引接口的一个索引,一个只读索引nnget; /返回数列中的指定数字返回数列中的指定数字nnnclass MyThree: DataSeries /实现接口实现接口DataSeriesnnint x;npublic MyThree()x=0; /构造函数构造函数npublic int Next /接口属性的实现。获取或设置值接口属性的实现。获取或设置值nngetx+=3; return x;nsetx=value;nnpublic int thisint index /实现索引实现索引nngetnnx=0;nfor(int i=0; iindex; i+)nx+=3;nreturn x;nn浪恫草羚低竹推耪重钥匣闭甥半颂誉虫颁惯褪籍桔消杀内语兵鞋镭偏赊策接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义nnclass App /应用类应用类n npublic static void Main()nnMyThree ob=new MyThree ();nfor (int i=0; i3; i+) /通过属性访问接口通过属性访问接口nConsole.WriteLine(Next value is + ob.Next);nConsole.WriteLine(Starting at 100);nob.Next=100;nfor (int i=0; i3; i+) /通过属性访问接口通过属性访问接口nConsole.WriteLine(Next value is + ob.Next);n n Console.WriteLine(Resetting to 0);n ob.Next=0;nfor (int i=0; i3; i+) /通过索引访问接口通过索引访问接口nConsole.WriteLine(Next value is + ob.Next);nn屈淆款郊姨拜智梧碱户铅之增驾篡修策表份嗅回子圆二畜享尽逞败餐尼范接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义n程序输出结果如下:程序输出结果如下:nNext value is 3nNext value is 6nNext value is 9nStarting at 100nNext value is 103nNext value is 106nNext value is 109nResetting to 0 nNext value is 3nNext value is 6nNext value is 9 拭萍钧绵柏晓撑瑰徒蟹瑶户埔显评焚黎栽闰神楷祥俯鳞苛痛淫哭诚甜耳翅接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.2 接口的定义接口的定义n(4) 接口的事件成员声明接口的事件成员声明有关有关“事件事件”在本章的最后一节介绍,本处只给出接口事件的声明格式:在本章的最后一节介绍,本处只给出接口事件的声明格式:n代码属性代码属性 new event 事件代理名事件代理名 事件名;事件名;n事件声明中的事件声明中的代码属性代码属性和和new修饰符的用法和作用,与接口的方法成修饰符的用法和作用,与接口的方法成员和属性成员的声明含义完全一样。员和属性成员的声明含义完全一样。n例如:例如:nInterface IAnn /其他成员的定义其他成员的定义nevent Click MyEvent;n啦强箩打他毅感盐嘲抖塞吞竟芦店松豹兢默躲柜碰册轮史牺向乘卢执羹汪接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用 n以上我们只是定义了一个接口,而在接口中声明的方法、属性、索引或以上我们只是定义了一个接口,而在接口中声明的方法、属性、索引或事件的真正实现是由类来完成的。所以说,一旦定义了接口,一个或更事件的真正实现是由类来完成的。所以说,一旦定义了接口,一个或更多的类就可以以不同的方式来实现该接口中的功能,并且每个类必须实多的类就可以以不同的方式来实现该接口中的功能,并且每个类必须实现该接口中所定义的所有方法、属性、索引或事件。即一个接口可以由现该接口中所定义的所有方法、属性、索引或事件。即一个接口可以由多个类来实现,而在一个类中也可以实现一个或多个接口。多个类来实现,而在一个类中也可以实现一个或多个接口。n实现接口的方式与继承相同,即将接口放在类名的后面,中间用冒号隔实现接口的方式与继承相同,即将接口放在类名的后面,中间用冒号隔开。实现接口的语句格式:开。实现接口的语句格式:nclass 类名:接口名列表类名:接口名列表nn /类实体类实体nn 其中接口名是指该类所要实现的接口的名称。其中接口名是指该类所要实现的接口的名称。珠褥烽释泌啥袒贿喳鼠瓢司桅形贡蛇左咯痛龙洒养奋乙甘礁征撵辰碑殖菇接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用 n1. 一个类实现(使用)一个接口。n【例【例10.4】创建一个类】创建一个类MyThree,实现【例,实现【例10.1】中定义的接口】中定义的接口DataSeries,用于生成一系列公差为,用于生成一系列公差为3的数列。的数列。nusing System;npublic interface DataSeries /接口接口DataSeriesnnint getNext(); /返回数字系列中的下一个数字返回数字系列中的下一个数字nvoid reset(); /重新开始重新开始nvoid setStart(int x); /设置初始值设置初始值nnclass MyThree: DataSeries /实现接口实现接口DataSeries的类的类nnint start;nint value;npublic MyThree() /构造函数构造函数nnstart=0;nvalue=0;nnpublic int getNext() /实现接口实现接口DataSeries中的方法中的方法getNext()nnvalue+=3;nreturn value;n笋蛛续泰朔怀释琳斥夯搅梅擦展突玄滴脐遭母厄邹岛贞屉盒憾粤夸烫资酒接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用 npublic void reset() /实现接口实现接口DataSeries中的方法中的方法reset()nnstart=0;nvalue=0;nnpublic void setStart(int x) /实现接口实现接口DataSeries中的方法中的方法setStart()nnstart=x;nvalue=x;nnnclass AppDataSeries /应用类应用类nnpublic static void Main()nnMyThree ob=new MyThree ();nfor (int i=0; i3; i+) /循环输出循环输出3个数字个数字nConsole.WriteLine(Next value is +ob.getNext();nConsole.WriteLine (Resstting); /重新开始重新开始nob.reset();nfor (int i=0; i3; i+) /循环输出循环输出3个数字个数字nConsole.WriteLine(Next value is +ob.getNext();nConsole.WriteLine (Starting at 100); /从从100重新开始重新开始nob.setStart(100);nfor (int i=0; i3; i+) /循环输出循环输出3个数字个数字nConsole.WriteLine(Next value is +ob.getNext();nn兰脖赞逝贰刺娟踌腊迄熬亡谴啦驻恒淖吼搁姚凋挤词渍洒差匀开暖倾雇蜡接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用 n程序的运行结果:程序的运行结果:nNext value is 3nNext value is 6nNext value is 9nResettingnNext value is 3nNext value is 6nNext value is 9nStarting at 100nNext value is 103nNext value is 106nNext value is 109n可见,要实现接口,必须在类名后包括接口,然后提供接口的每一个成员的实现。注意观可见,要实现接口,必须在类名后包括接口,然后提供接口的每一个成员的实现。注意观察上例中接口成员和类中相应的实现的访问类型的写法,在接口成员的声明中不需要任何察上例中接口成员和类中相应的实现的访问类型的写法,在接口成员的声明中不需要任何访问修饰符,而在类中相应接口成员实现定义中则都用访问修饰符,而在类中相应接口成员实现定义中则都用public修饰符。修饰符。作龟源这赔狠艰淡芳红涯台乐吧芳扒擞执扩娶妮谈神镁鄂洲帐沦寒纠沸姆接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用 n类除了可以用来实现接口外,还可以定义它自己的额外成员,如我们在类除了可以用来实现接口外,还可以定义它自己的额外成员,如我们在MyThree类中再添类中再添加一个方法加一个方法getPrevious(),用于返回前一个数字的值。,用于返回前一个数字的值。nclass MyThree: DataSeries /实现接口实现接口DataSeries,并添加,并添加getPrevious()方法方法nnint start;nint value;nint prev;npublic MyThree() /构造函数构造函数nnstart=0;nvalue=0;nprev= -3;nnpublic int getNext() /实现接口实现接口DataSeries中的方法中的方法getNext()nnprev=value;nvalue+=3;颓贞母擒晃携靶檄疙痞兹溪赴鸡矾修炉勉拇趁饺娜鹊偿咎蒙月督蕊屏找庐接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用 nreturn value;nnpublic void reset() /实现接口实现接口DataSeries中的方法中的方法reset()nnstart=0;nvalue=0;nprev=-3;nnpublic void setStart(int x) /实现接口实现接口DataSeries中的方法中的方法setStart()nnstart=x;nvalue=x;nprev=x-3;nnint getPrevious() /DataSeries接口没有定义的方法接口没有定义的方法nnreturn prev;nnn程序中加黑的语句都是由于添加了程序中加黑的语句都是由于添加了getPrevious()方法,而改变了接口方法,而改变了接口DataSeries的方法实现。但是这些方法的接口定的方法实现。但是这些方法的接口定义没有任何变化,只是实现接口的类中增加了功能代码,这也是接口的优点之一。义没有任何变化,只是实现接口的类中增加了功能代码,这也是接口的优点之一。菏双鹅做八扮令苦絮脉贬砂蚌悄耳枚弟绍袄鲤红卓伊佰突靠蓬易醒捶纲夕接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用n2. 多个类实现(使用)一个接口n【例例10.5】我们再用另一个类MyFour来实现接口DataSeries,生成一系列公差为4的数字序列。nclass MyFour: DataSeries /用另一种方式实现接口DataSeriesnnint start;nint value;npublic MyFour() /构造函数nnstart=0;nvalue=0;nnpublic int getNext() /实现接口DataSeries中的方法getNext()nnvalue+=4;石笋癌散统陶阅谬补啼誓酮妆尺慑理帜翁姆惫淹蹄哨糠阶横绣眉镭翌胞傀接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用nreturn value;nnpublic void reset() /实现接口DataSeries中的方法reset()nnstart=0;nvalue=0;nnpublic void setStart(int x) /实现接口DataSeries中的方法setStart()nnstart=x;nvalue=x;nnn可以把此处定义的类加到【例10.4】中,则在应用类中的对象可以使用MyFour类的方法来生成公差是4的数列。具体代码构成,用户可自己组织。由此可以看出MyThree和MyFour这些类的对象的接口都是DataSeries接口,也就是说,任何一个类的对象都可以分别实例化接口中定义的功能信息,即接口实现了C#的“一个接口,多种方法”的多态性。霄窃趣嘛顾映摔妹旭讥洛胺犹缠敬厚炽珍帮染词侣鸭让葱爱世钮月石仔垄接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用n3. 一个类实现(使用)多个接口n使用接口而不使用继承的最大的好处就是,可以在同一个类使用接口而不使用继承的最大的好处就是,可以在同一个类中同时实现多个接口,即实现多重继承。中同时实现多个接口,即实现多重继承。n要实现多个接口,需将这些接口用逗号分开。要实现多个接口,需将这些接口用逗号分开。福袋磋近埂闸狠抠裔序藩歌扼羞诽截兹俄份郝顶赞酪虾枝技藤血辛械粥祥接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用n【例【例10.6】在同一个类中实现多个接口。】在同一个类中实现多个接口。nusing System;npublic interface IShape /图形图形接口接口IShapenndouble Area(); /接口方法,计算图形面积接口方法,计算图形面积ndouble GramLength(); /接口方法,计算图形边长接口方法,计算图形边长nint Sides get; /接口属性,获取图形的边长接口属性,获取图形的边长nnpublic interface IShapePlay /输出计算结果的接口输出计算结果的接口IShapePlaynnvoid Play();nnpublic class Square: IShape, IshapePlayn/实现两个接口的类实现两个接口的类Square,计算正方形面积,计算正方形面积nnprivate int sides; /边数边数npublic int SideLength; /边长边长npublic Square() sides=4; /构造函数构造函数npublic int Sides get return sides; /实现接口实现接口IShape中的属性中的属性npublic double Area()n/实现接口实现接口IShape中的方法,计算正方形面积中的方法,计算正方形面积nnreturn (double) (SideLength* SideLength);扑解马唇杯所俭利朱送宾唬抱仔枷编确冲稿玖俘眼把许邑跪肾枯毒逝炎霸接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用nnpublic double GramLength () /实现接口实现接口IShape中的方法,计算边长中的方法,计算边长nnreturn (double) (Sides* SideLength);nnpublic void Play() /实现接口实现接口IShapePlay中的方法,输出计算结果中的方法,输出计算结果nnConsole.WriteLine(n计算正方形面积结果如下:计算正方形面积结果如下:);nConsole.WriteLine(边长:边长:0, this.SideLength);nConsole.WriteLine(边数:边数:0, this.Sides);nConsole.WriteLine(面积:面积:0, this.Area();nnnpublic class MyApp /应用类应用类nnpublic static void Main()nnSquare sq=new Square();nsq.SideLength=8;nsq.Play();nn世鳖秘来捞汰氛据秤态顶优圈钻靴课涵拽名文嫂腋逾怨庄瓷诌融罗甘秦灸接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.3 接口的实现与使用接口的实现与使用n程序运行结果:程序运行结果:n计算正方形面积结果如下:计算正方形面积结果如下:n边长:边长:8n边数:边数:4n面积:面积:64n从从Square类代码可见,由于该类包含了两个接口,所以它必须实现这两类代码可见,由于该类包含了两个接口,所以它必须实现这两个接口的所有成员。个接口的所有成员。 驳应萍董卫忙奋明靛燕喳呢哪蕊卫膝涛粪叙算坐受氛理痘或吴陋嘶懦士可接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.4 接口映射接口映射 n接口映射是指在实现接口的类或结构中定位接口成员的实现的过程。接口映射是指在实现接口的类或结构中定位接口成员的实现的过程。n在映射过程中,判断类的某个成员在映射过程中,判断类的某个成员Mc与接口的相应成员与接口的相应成员Mi匹配的原则是:匹配的原则是:l如果如果Mc和和Mi是方法,且它们的名称、返回类型和形式参数都一致,则是方法,且它们的名称、返回类型和形式参数都一致,则Mc和和Mi匹配。匹配。l如果如果Mc和和Mi是属性,它们的名称和返回类型都一致,且访问器也相同是属性,它们的名称和返回类型都一致,且访问器也相同(如果如果Mc不是显式不是显式实现,则允许实现,则允许Mc具有额外的访问器具有额外的访问器),则,则Mc和和Mi匹配。匹配。l如果如果Mc和和Mi是索引器,它们的名称和形式参数都一致,且具有相同的访问器是索引器,它们的名称和形式参数都一致,且具有相同的访问器(如果如果Mc不不是显式实现,则允许是显式实现,则允许Mc具有额外的访问器具有额外的访问器),则,则Mc和和Mi匹配。匹配。n现假设在类或结构现假设在类或结构C上执行映射过程,查找接口上执行映射过程,查找接口I的成员的成员M的实现,查找过程如下:的实现,查找过程如下:l在类在类C中查找中查找I.M的实现。如果的实现。如果C中包含一个与中包含一个与I和和M相匹配的显式接口成员实现,则这个成相匹配的显式接口成员实现,则这个成员就被认为是员就被认为是I.M的实现。的实现。l如果在如果在C中没有找到显式接口成员实现,则查找中没有找到显式接口成员实现,则查找C的所有非静态公共成员,如果某个成员与的所有非静态公共成员,如果某个成员与M相匹配,则被认为是相匹配,则被认为是I.M的实现。的实现。l如果在如果在C中没有找到以上的两种匹配成员,则转到中没有找到以上的两种匹配成员,则转到C的基类中继续查找,直到找到匹配的实的基类中继续查找,直到找到匹配的实现。如果一个没有找到,将会提示出错。现。如果一个没有找到,将会提示出错。场涧厢投晾沪尺耙喊输喊便疑砌阐馁缀拧锋氨选两瓤葡画催曰圾舟该达段接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.4 接口映射接口映射 n例如:例如:nclass MyClass: Interface1 Class1 Interface2 Class2nnvoid MyMethod1() . /方法体代码方法体代码 nvoid MyMethod2() /方法体代码方法体代码 nn以上代码中,以上代码中,MyClass类使用的类使用的MyMethod1()方法是在接口方法是在接口Interface1中声明的。当执行上述代码时,先在中声明的。当执行上述代码时,先在MyClass类中查找类中查找Interface1接口成员的实现,由于在类接口成员的实现,由于在类MyClass中找到了中找到了MyMethod1()方法的实现,所以就终止了查找运算。然而,如果在方法的实现,所以就终止了查找运算。然而,如果在MyClass类没有类没有找到找到MyMethod1()方法的实现,则还将查找基类方法的实现,则还将查找基类Class1和和Class2。嗅搽怠揭滴腋湾骋舀萨佯蒜字敝呸并漾螟宏净匈纳坤范培膜阜牲茁嫌骤滋接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.4 接口映射接口映射 n如果类或结构实现了多个包含相同成员的接口,则这些成员可能会映射到同一个实现上。在以下代码段如果类或结构实现了多个包含相同成员的接口,则这些成员可能会映射到同一个实现上。在以下代码段中,接口中,接口IA和和IB的的Play方法成员都被映射到类方法成员都被映射到类C的的Play()方法的实现上,由于两个接口的同名方法一方法的实现上,由于两个接口的同名方法一定是实现不同的显示目标,则当我们调用定是实现不同的显示目标,则当我们调用Play()方法时将会导致歧义性:方法时将会导致歧义性: ninterface IA /接口接口IAnnvoid Play (); /方法成员方法成员nninterface IB /接口接口IBnnviod Play(); /方法成员方法成员nnclass C: IA,I Bnnpublic void Play() nn所以在应用问题中,应根据需要使用显式接口成员实现,来分别为各个接口的成员提供实现。所以在应用问题中,应根据需要使用显式接口成员实现,来分别为各个接口的成员提供实现。榴离汞哗涂捶胶畸理瘪盯馅佳阵组激龋滓汕酌壳剪雌射筹弹彤饺察缅盖傣接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 n“显式接口成员实现显式接口成员实现”是指在类中使用接口成员是指在类中使用接口成员(方法、属性、索引或事件方法、属性、索引或事件)的完全限定名的完全限定名来定义实现接口成员。接口成员的完全限定名是指依次用点运算符连接命名空间名、最高来定义实现接口成员。接口成员的完全限定名是指依次用点运算符连接命名空间名、最高层基接口名和各层的派生接口名,最后加上成员名构成整个访问名。例如:层基接口名和各层的派生接口名,最后加上成员名构成整个访问名。例如:nnamesapce MySpacenninterface IMyinterfacennvoid MyMethod();nninterface IYouinterfacennvoid YourMethod();nnn方法方法MyMethod()的完全限定名为的完全限定名为MySpace.IMyinterface.MyMethod()。皑山轰浸休骆条明叶袭拾继俗妙崭服计樱卫撩漠僵速贪颊尹甥癸媳肉剐仓接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 n创建接口成员的显式实现有两个原因:创建接口成员的显式实现有两个原因:l一个类有可能要同时实现两个接口,如果每个接口都定义了同名、同类一个类有可能要同时实现两个接口,如果每个接口都定义了同名、同类型的成员,则在类中采用完全限定名来显式实现各接口中的相应成员,型的成员,则在类中采用完全限定名来显式实现各接口中的相应成员,就可以消除歧义。就可以消除歧义。l使用完全限定名来实现一个接口成员时,在类中该接口成员是私有的。使用完全限定名来实现一个接口成员时,在类中该接口成员是私有的。n在上述继承接口在上述继承接口IA和和IB的类的类C中就必须使用显式接口成员实现来消除歧中就必须使用显式接口成员实现来消除歧义性:义性:nclass C: IA,I Bnnvoid IA.Play() /这里不能用这里不能用public,是私有成员,是私有成员nvoid IB.Play() /同上同上绿两疤队锨梢训惟依拿看玖顶鹤惕叠宠榷度叼揽返笺罢酪阳邹饭畅迭缸校接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 n【例【例10.7】将【例】将【例10.6】计算正方形面积的代码修改如下:】计算正方形面积的代码修改如下:nusing System;npublic interface IShapenndouble Area(); /接口方法,计算图形面积接口方法,计算图形面积ndouble GramLength(); /接口方法,计算图形边长接口方法,计算图形边长nint Sides get; /接口属性,获取图形的边长接口属性,获取图形的边长nvoid Play(); /接口方法接口方法nnpublic interface IShapePlay nnvoid Play(); /接口方法接口方法nnpublic class Square: IShape, IShapePlay /实现两个接口的类实现两个接口的类Squarennprivate int sides; /边数边数npublic int SideLength; /边长边长npublic Square()sides=4; /构造函数构造函数npublic int Sides get return sides; /实现接口实现接口IShape中的属性中的属性npublic double Area() /实现接口实现接口IShape中的方法,计算面积中的方法,计算面积nnreturn (double) (SideLength* SideLength);nnpublic double GramLength() /计算边长计算边长nnreturn (double) (Sides* SideLength);n恨哩扔账粪支爱彼便冻展芭锹胆槐深谋诌瑚烛悄司瞳胡侧招磊场搔梅礼叮接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 n/显示实现显示实现IShape接口中的接口中的Play()方法,输出文本信息方法,输出文本信息nvoid IShape.Play() /注意:这个显式实现是个私有成员,不能用注意:这个显式实现是个私有成员,不能用public修饰修饰nnConsole.WriteLine(n计算面积结果如下:计算面积结果如下:);nConsole.WriteLine(边长:边长:0, this.SideLength);nConsole.WriteLine(边数:边数:0, this.Sides);nConsole.WriteLine(面积:面积:0, this.Area();nn/显示实现接口显示实现接口IShapePlay中的中的Play()方法,图形显示方法,图形显示nvoid IShapePlay.Play() /注意:这个显式实现是个私有成员,不能用注意:这个显式实现是个私有成员,不能用publicn修饰修饰nnConsole.WriteLine(显示图形如下:显示图形如下:);nn碑祁访番谓仍柒释幢摆弓丽锄俺零骆高回迫吾渺潍霸龙溃捣颜硝洼投峻摔接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 npublic class MyApp /应用类应用类nnpublic static void Main()nnSquare sq=new Square(); /创建一个对象创建一个对象 sqnsq.SideLength=8;n/定义一个定义一个IShape接口变量接口变量sh。通过强制转换将对象。通过强制转换将对象sq看作一个接口看作一个接口IShapenIShape sh=( IShape)sq; n/定义一个定义一个IShapePlay接口变量接口变量shp,原理同上,原理同上nIShapePlay shp=( IShapePlay)sq; nsh.Play(); /通过接口变量通过接口变量sh调用显式定义方法调用显式定义方法IShape.Play()nshp.Play(); /通过接口变量通过接口变量shp调用显式定义方法调用显式定义方法IShapePlay.Play()nnn程序输出结果:程序输出结果:n计算面积结果如下:计算面积结果如下:n边长:边长:8n边数:边数:4n面积:面积:64n图形显示如下:图形显示如下:羊减困脏览凳考砸魔掀频赃优吟嗽轿淬寒乖爬刺严懒氖偷袒芳论洲埃羹榜接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 n本例使用了方法的完全限定名本例使用了方法的完全限定名(接口名接口名.方法名方法名),在同一个类,在同一个类Square中中显式地实现了两个接口显式地实现了两个接口IShape和和IShapePlay中的同名方法中的同名方法Play(),在,在应用类中对定义的显式方法进行了调用。对显式实现的方法的调用不能应用类中对定义的显式方法进行了调用。对显式实现的方法的调用不能使用使用“类名类名.方法方法”的格式,因为用类名调用方法不能明确调用哪个方法,的格式,因为用类名调用方法不能明确调用哪个方法,所以,要调用不同接口的某个同名方法时,一般有两个常用的方法:所以,要调用不同接口的某个同名方法时,一般有两个常用的方法:l本例中的方法。将类对象强制转换为相应的接口变量,在本例中就是将本例中的方法。将类对象强制转换为相应的接口变量,在本例中就是将类对象类对象sq强制转换为接口强制转换为接口IShape或或IshapePlay的两个相应接口变量的两个相应接口变量sh和和shp,可以用这些接口变量来调用相应的接口方法,可以用这些接口变量来调用相应的接口方法Play()。l在实现方法的类中,分别显式实现两个同名方法,由于显示实现的方法在实现方法的类中,分别显式实现两个同名方法,由于显示实现的方法是私有的,所以在类中再相应创建两个用于对这两个显式方法进行调用是私有的,所以在类中再相应创建两个用于对这两个显式方法进行调用的调用方法,称其为接口引用调用方法。在应用类中则只要像正常情况的调用方法,称其为接口引用调用方法。在应用类中则只要像正常情况下用下用“对象名对象名.接口引用调用方法名接口引用调用方法名”调用方法即可。这种方法的代码如调用方法即可。这种方法的代码如下,接口代码同【例下,接口代码同【例10.8】,以下只给出实现方法的类和应用类代码:】,以下只给出实现方法的类和应用类代码:拜洁拌键盘猾咳乖忧诽靴豁罐分叛船曼藐帮傈藻捂奥褂捞奠性胞狙撩擎立接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 npublic class Square: IShape, IShapePlay /实现两个接口的类实现两个接口的类Squarennprivate int sides; /边数边数npublic int SideLength; /边长边长npublic Square()sides=4; /构造函数构造函数npublic int Sides get return sides; /实现接口实现接口IShape中的属性中的属性npublic double Area() /实现接口实现接口IShape中的方法,计算面积中的方法,计算面积nnreturn (double) (SideLength* SideLength);nnpublic double GramLength() /计算边长计算边长nnreturn (double) (Sides* SideLength);nn/显示实现显示实现IShape接口中的接口中的Play()方法,输出文本信息方法,输出文本信息nvoid IShape.Play() /注意:这个显式实现是个私有成员,不能用注意:这个显式实现是个私有成员,不能用public修饰修饰nnConsole.WriteLine(n计算面积结果如下:计算面积结果如下:);nConsole.WriteLine(边长:边长:0, this.SideLength);nConsole.WriteLine(边数:边数:0, this.Sides);nConsole.WriteLine(面积:面积:0, this.Area();n桨是仗扭撞奴辉象擂掸揪祸忠老捆勘绘筐添鳃斋雄遵薯陶狰敲桑溢虑播渗接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.5 显式接口成员实现显式接口成员实现 n/显示实现接口显示实现接口IShapePlay中的中的Play()方法,图形显示方法,图形显示nvoid IShapePlay.Play() /注意:这个显式实现是个私有成员,不能用注意:这个显式实现是个私有成员,不能用public修饰修饰nnConsole.WriteLine(显示图形如下:显示图形如下:);nn/通过接口引用调用方法通过接口引用调用方法Play()npublic void Play_A()nnIShape ob_a=this; /当前对象的引用当前对象的引用nob_a.Play(); /调用调用IShape.Play()方法方法nnpublic void Play_B()nnIShapePlay ob_b=this; /当前对象的引用当前对象的引用nob_b.Play(); /调用调用IShapePlay.Play()方法方法nnnpublic class MyApp /应用类应用类nnpublic static void Main()nnSquare sq=new Square(); /创建一个对象创建一个对象 sqnsq.SideLength=8;nsq.Play_A(); /调用调用IShape.Play()方法方法nsq.Play_B(); /调用调用IShapePlay.Play()方法方法nn忻姓耳焚渤蹈绵驭屹勿赐区陈助静贼皿熊唱书苛优钙懦哑扑浑牙萧码状跪接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n1. 接口之间的继承n在在C#中,一个接口可以继承另一个接口,语法和类的继承一样。当一个中,一个接口可以继承另一个接口,语法和类的继承一样。当一个类要实现从另一个接口继承来的接口,该类就必须提供接口继承链中定类要实现从另一个接口继承来的接口,该类就必须提供接口继承链中定义的所有成员的实现。义的所有成员的实现。附首寸橱伦蓬湛峻凸梆迂拆细灰均脆松玻腊蒙胎脱颓岳讨时凸粉轨像略蒲接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n【例【例10.8】一个接口继承另一个接口。】一个接口继承另一个接口。nusing System;npublic interface IA /接口接口IAnnvoid meth1();nvoid meth2();nnpublic interface IB:IA /接口接口IB继承接口继承接口IA nvoid meth3();nnclass Myclass: IB /该类必须实现接口该类必须实现接口IA和和IB的所有成员的所有成员nnpublic void meth1()nnConsole.WriteLine(实现实现meth1()方法方法);nnpublic void meth2 ()nnConsole.WriteLine(实现实现meth2()方法方法);买敌毫侵腺叼颜疵禁须圾杖顾馅聚职梅彬黄衬专慨戳相松盼烷赣浙弘泣赴接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 nnpublic void meth3()nnConsole.WriteLine(实现实现meth3()方法方法);nnnclass Appnnpublic static void Main()nnMyclass ob=new Myclass ();nob.meth1();nob.meth2();nob.meth3();nnn程序运行结果:程序运行结果:n实现实现meth1()方法方法n实现实现meth2()方法方法n实现实现meth3()方法方法升举母厨由写烹剪双曾澄龄靶罩祷选叶沫装妒劫冉症碍滥瓷埠伯栖恰家腺接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n当一个接口继承另一个接口时,如果派生接口的成员和基接口的成员同名,则基当一个接口继承另一个接口时,如果派生接口的成员和基接口的成员同名,则基接口的成员就会被隐藏,这与类继承时发生的情况一样。如果派生接口的成员不接口的成员就会被隐藏,这与类继承时发生的情况一样。如果派生接口的成员不用用new修饰符,编译时会发出警告信息。加上修饰符,编译时会发出警告信息。加上new修饰符后即可消除警告。读修饰符后即可消除警告。读者可以通过对本例代码的适当修改,来观察隐藏基接口成员的现象。者可以通过对本例代码的适当修改,来观察隐藏基接口成员的现象。n如果某个接口含有隐藏成员,只有使用如果某个接口含有隐藏成员,只有使用new修饰符的成员可被调用。在以下代修饰符的成员可被调用。在以下代码中,接口码中,接口ISon中的方法成员中的方法成员X()隐藏了基接口隐藏了基接口IFather中的属性成员中的属性成员X: ninterface IFathernnint X get;nninterface ISon : IFathernnnew int X(); /此处的方法成员此处的方法成员X()将隐藏基接口中的同名属性成员将隐藏基接口中的同名属性成员Xn倦梭邻检著挥阅再串谩售麻仆鉴气戍蹄阁遗肮衡湃亡纪杏惺滨存谨贱寂梗接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n对被隐藏的属性成员对被隐藏的属性成员X必须提供显式接口成员实现来消除冲突,以下给出了在类中的必须提供显式接口成员实现来消除冲突,以下给出了在类中的3种不同的实现方法:种不同的实现方法:l两个接口中的成员都使用显式接口成员实现方式来实现两个接口中的成员都使用显式接口成员实现方式来实现nclass C : ISonnnint IFather.X get /使用显式接口成员方式使用显式接口成员方式nint ISon.X() /使用显式接口成员方式使用显式接口成员方式nl接口接口ISon中的中的X成员使用显式接口成员实现方式来实现成员使用显式接口成员实现方式来实现nclass C: ISonnnpublic int X get nint ISon.X( ) /使用显式接口成员方式使用显式接口成员方式nl接口接口IFather中的中的X成员使用显式接口成员实现方式来实现成员使用显式接口成员实现方式来实现nclass C : ISonnnint IFather.X get /使用显式接口成员方式使用显式接口成员方式nint X() n涣胁顶豌捣望菠鞭讹矗吹灶俏霹播煮乱灯超奸歇浑诞式舆型捍鸳笼青年瑟接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n在多重继承的情况下,如果基接口的一个成员被它的一个派生接口中的同名成员隐藏,则该成员也将在这个派生接口的所有派生接口中都处于隐在多重继承的情况下,如果基接口的一个成员被它的一个派生接口中的同名成员隐藏,则该成员也将在这个派生接口的所有派生接口中都处于隐藏状态。例如:藏状态。例如:ninterface IFather /基接口基接口IFathernnvoid F(int x);nninterface IBoy: IFather /派生接口派生接口IBoynnnew void F(int x); /隐藏基接口隐藏基接口IFather中的成员中的成员F()nninterface IGirl: IFather /派生接口派生接口IGirlnnvoid G();nninterface IMuilt : IBoy, IGirl /也隐藏也隐藏IBoy的基接口的基接口IFather中的成员中的成员F()nclass Appnnvoid method(IMuilt y)nny.F(1); /调用调用IBoy.F,因为,因为IFather成员被派生接口成员被派生接口IBoy中的中的F()成员所隐藏成员所隐藏n(IFather)y).F(1);/调用调用IFather.Fn(IBoy)y).F(1);/调用调用IBoy.Fn(IGirl)y).F(1);/调用调用IFather.F,IFather成员成员F()没有被派生接口没有被派生接口IGirl中的成中的成n员所隐藏员所隐藏nn或迷盂疲牧涪垣怂蟹鼻刮裙权肢跑海鲁幕尸篮撮主尊稻籍液投异憾枕仕皿接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n2. 类从基类继承接口映射n类可以从基类继承接口映射,现观察以下代码段:类可以从基类继承接口映射,现观察以下代码段:nusing System;ninterface IAnnvoid Play();nnclass Father: IA /实现接口实现接口IAnnpublic void Play()Console.WriteLine(调用基类方法调用基类方法Play();nnclass Son: Father /继承类继承类Fathern /隐藏基类方法隐藏基类方法Play()nnew public void Play()Console.WriteLine(调用派生类的新方法调用派生类的新方法Play(); nnclass Appnnpublic static void Main()nnFather f=new Father(); /创建对象实例创建对象实例nSon s=new Son();nIA fa=f; /把对象实例赋给接口实例把对象实例赋给接口实例nIA sa=s;nf.Play(); /调用调用Father.Play()ns.Play(); /调用调用Son.Play()nfa.Play(); /调用调用Father.Play()nsa.Play(); /调用调用Father.Play()nn吵棋绕厢虚纪裴诲辙信牺欢姐叭坟绒酣央饺条颈聋漳鹤执印色穿钥旷磕亩接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n程序运行结果:程序运行结果:n调用基类方法调用基类方法Play()n调用派生类的新方法调用派生类的新方法Play()n调用基类方法调用基类方法Play()n调用基类方法调用基类方法Play()n在上述代码中,显然派生类在上述代码中,显然派生类Son中的中的Play()方法隐藏了基类方法隐藏了基类Father中的中的Play()方法,但这并没有改变方法,但这并没有改变IA.Play()到到Father.Play()之间的映射关系。上述代码之间的映射关系。上述代码中中if.Play()和和is.Play()语句只调用语句只调用Father类的类的Play()方法,这是因为,派生类方法,这是因为,派生类Son中引入新成员中引入新成员method()方法,这并未改变方法,这并未改变Father类和类和IA接口之间的映射接口之间的映射关系。当然这种情况发生显然不是我们所希望的,我们有时总是希望不同的对象关系。当然这种情况发生显然不是我们所希望的,我们有时总是希望不同的对象使用不同的实现方法,如何解决这种问题呢?使用不同的实现方法,如何解决这种问题呢? 遁杆盐垦睁必趴键了曼疼隘昼印膘劈说谢额吞忻插骋胖鼻馒砸挠丧茅扰铆接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n3. 将接口成员映射到虚方法上n当在接口方法和类的虚方法间创建映射时,其派生类可以重载虚拟方法。所以我们可以将接口方法映射当在接口方法和类的虚方法间创建映射时,其派生类可以重载虚拟方法。所以我们可以将接口方法映射到类中的一个虚方法上,这样,派生类就可通过覆盖基类的虚方法来改变接口的实现,现将上面代码中到类中的一个虚方法上,这样,派生类就可通过覆盖基类的虚方法来改变接口的实现,现将上面代码中的类的类Father的的Play()方法定义为虚方法:方法定义为虚方法:nusing System;ninterface IAnnvoid Play();nnclass Father: IA /实现接口实现接口IAnn /通过虚方法来实现接口方法通过虚方法来实现接口方法npublic virtual void Play()Console.WriteLine(调用基类方法调用基类方法Play(); nnclass Son: Father /继承类继承类Fathernn/在派生类中覆盖基类在派生类中覆盖基类Father的方法的方法Play()npublic override void Play()Console.WriteLine(调用派生类的新方法调用派生类的新方法Play(); nnclass App褥浇话酉瘴认蝇牺撵惰杜重睹敲前错小睡蒜逊蜜弥手荷邑男荆恋益调怯躁接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 nnpublic static void Main()nnFather f=new Father(); /创建对象实例创建对象实例nSon s=new Son();nIA fa=f; /把对象实例赋给接口实例把对象实例赋给接口实例nIA sa=s;nf.Play(); /调用调用Father.Play()ns.Play(); /调用调用Son.Play()nfa.Play(); /调用调用Father.Play()nsa.Play(); /调用调用Son.Play(),与上面代码的调用结果不同,与上面代码的调用结果不同nnn程序运行结果:程序运行结果:n调用基类方法调用基类方法Play()n调用派生类的新方法调用派生类的新方法Play()n调用基类方法调用基类方法Play()n调用派生类的新方法调用派生类的新方法Play()喀钥模腺烃独苹呈妇棕劫帛袒涩捷疑峪荆鹊病闰各颈桌芝蛹鲤详卒煞窟骇接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承 n显式接口成员实现时不能被定义为虚成员。但可在显式接口成员实现的过程中调用类的一个虚方法,由于可以在派生类显式接口成员实现时不能被定义为虚成员。但可在显式接口成员实现的过程中调用类的一个虚方法,由于可以在派生类中覆盖这个虚方法,所以同样可以达到调用不同方法的目的。中覆盖这个虚方法,所以同样可以达到调用不同方法的目的。nusing System;ninterface IAnnvoid Play();nnclass Father:IA /实现接口实现接口IAnn void IA.Play() /显示成员实现显示成员实现nnConsole.Write(调用虚方法调用虚方法VirtualPlay():);nVirtualPlay();nnpublic virtual void VirtualPlay()Console.WriteLine(执行执行Father类中虚方法类中虚方法VirtualPlay(); nnclass Son: Father /继承类继承类Fathernn/在派生类中覆盖基类在派生类中覆盖基类Father的方法的方法Play()npublic override void VirtualPlay()Console.WriteLine(执行派生类的新方法执行派生类的新方法VirtualPlay(); n还馅一昼政妈浅题殃袋伤柔餐汛神习储锄撒皆领幕鸡妥逢殖彻桂施婆凌川接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承nclass Appnnpublic static void Main()nnFather f=new Father(); /创建对象实例创建对象实例nSon s=new Son();nIA fa=f; /把对象实例赋给接口实例把对象实例赋给接口实例nIA sa=s;nf.VirtualPlay(); /调用调用Father.VirtualPlay()ns.VirtualPlay(); /调用调用Son.VirtualPlay()nfa.Play(); /调用调用Father类中类中IA.Play()nsa.Play(); /调用调用Son.VirtualPlay()nnn程序运行结果:程序运行结果:n执行执行Father类中虚方法类中虚方法VirtualPlay()n执行派生类的新方法执行派生类的新方法VirtualPlay()n调用虚方法调用虚方法VirtualPlay():执行:执行Father类中虚方法类中虚方法VirtualPlay()n调用虚方法调用虚方法VirtualPlay():执行派生类的新方法:执行派生类的新方法VirtualPlay()靛鳞咋旋狐巴慨涎技督巫饱底榜穷生犬陷吐丽隔比丫狂知炸柠辉似寐丙渴接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.6 接口实现的继承接口实现的继承n4. 将接口成员映射到抽象方法上n接口成员不仅可以映射到虚方法上,也可以映射到抽象方法上接口成员不仅可以映射到虚方法上,也可以映射到抽象方法上(也叫纯虚方法也叫纯虚方法),所以抽象类也可以实现接口。所以抽象类也可以实现接口。ninterface IAnnvoid F();nvoid G();nnabstract class AbsClass: IA /使用抽象类实现接口使用抽象类实现接口nnpublic abstract void F();npublic abstract void G();n溅曳疆嚣痈丑炮疙钒酉釉凛曳樟咬庸秘酬怜念件腹尤癌粪厄初旗啥揩肥稠接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.7 接口的重新实现接口的重新实现 n接口的重新实现是指某个类可以再次实现已被它的基类实现的接口,即继承借口实现的类可以重新实现该接口。其方法是把接口的名称置于基类列表中,当重新实现接口后,派生类从基类继承的接口映射不会影响为接口的重实现建立的接口映射。卷窥派荆搔遏奖紊柜冻英坟焙兜吻婶耘鸳饱畦集涪瓦滤满依霍粤呻怨化狗接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.7 接口的重新实现接口的重新实现 n【例【例10.9】在以下代码中,类】在以下代码中,类CFather实现接口实现接口IFather,类,类CSon从类从类CFather派生,并重新实现了派生,并重新实现了IFather接口的部分成员接口的部分成员(Play()方法和方法和Clear()方法方法)。nusing System;nnamespace Ifacennpublic interface IFather /接口接口IFathernnint x get; set; /属性属性n int y get; set; nvoid Play(); /方法方法nvoid Clear();nnclass CFather : IFather /类类CFathernnpublic int x /实现接口实现接口IFather中的属性中的属性xnngetnnConsole.WriteLine(在在CFather类中实现属性类中实现属性x);nreturn 0;nnsetnnConsole.WriteLine(在在CFather类中实现属性类中实现属性x);nnnpublic int y /实现接口实现接口IFather中的属性中的属性ynngetnnConsole.WriteLine(在在CFather类中实现属性类中实现属性y);nreturn 0;nnset粱做横坎计隅习夷锈皖偶龋谜粮导卯渣翘讼逢漆斌庄盘赵磷郎汰墅娩寒病接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.7 接口的重新实现接口的重新实现 nnConsole.WriteLine(在在CFather类中实现属性类中实现属性y);nnnpublic void Play()nnConsole.WriteLine(在在CFather类中实现类中实现Play()方法方法);nnpublic void Clear()nnConsole.WriteLine(在在CFather类中实现类中实现Clear()方法方法);nnnclass CSon: CFather , IFathernnprivate int mx;nprivate int my;nprivate string mtext;nnew public void Play() /重新实现接口重新实现接口IFather中定义的中定义的Play()方法方法nnConsole.WriteLine(在在CSon类中实现类中实现Play()方法方法);nnpublic string Textnngetreturn mtext;nsetmtext=value;nnnew public void Clear() /重新实现接口重新实现接口IFather中定义的中定义的Clear()方法方法nnthis.Text=;nConsole.WriteLine(在在CSon类中实现类中实现Clear()方法方法);nn早杨桥丝柠厚哄苯褒甲姻屯核箱疤湃娶指间佑耀菜恶淫毡毯型耽懊骗赛鸡接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.7 接口的重新实现接口的重新实现 nclass Appnnpublic static void Main(string args)nnCSon ctext=new CSon();nConsole.WriteLine(通过类通过类CSon的实例调用接口成员的实例调用接口成员);nctext.x=0; /由于类由于类CSon中没有实现属性中没有实现属性x,所以调用其基类,所以调用其基类CFather中的中的nx属性实现属性实现nctext.y=0; /原因同上。原因同上。nctext.Play(); /类类CSon中的中的Play方法覆盖其基类方法覆盖其基类CFather中的中的Play方法方法nctext.Clear(); /原因同上。原因同上。nConsole.WriteLine();nConsole.WriteLine(通过接口通过接口IFather的实例调用接口成员的实例调用接口成员);nIFather itext=ctext;nitext.x=1; /接口接口IFather的属性的属性x被映射到类被映射到类CFather中实现中实现nitext.y=1; / 原因同上原因同上nitext.Play();/接口接口IFather中的中的Play方法被映射到派生类方法被映射到派生类CSon中中Play方方n法的实现上法的实现上nitext.Clear(); /原因同上原因同上nConsole.WriteLine();nConsole.WriteLine(通过类通过类CFather的实例调用接口成员的实例调用接口成员);nCFather obtext=ctext;n/通过类通过类CFather的对象调用的接口成员全部是类的对象调用的接口成员全部是类CFather提供的实现,提供的实现,n/说明说明CFather类的接口映射没有被类的接口映射没有被CSon类的重新实现所改变类的重新实现所改变nobtext.x=1; nobtext.y=1; nobtext.Play(); nobtext.Clear(); nnn琉狗侮缀搓顽一既邱橡喂钱寅果剁阵晓翱溶动啡顶推舵箔歧诽急供卷牛绰接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.7 接口的重新实现接口的重新实现 n程序运行结果:程序运行结果:n通过类通过类CSon的实例调用接口成员的实例调用接口成员n在在CFather类中实现属性类中实现属性xn在在CFather类中实现属性类中实现属性yn在在CSon类中实现类中实现Play()方法方法n在在CSon类中实现类中实现Clear()方法方法n通过接口通过接口IFather的实例调用接口成员的实例调用接口成员n在在CFather类中实现属性类中实现属性xn在在CFather类中实现属性类中实现属性yn在在CSon类中实现类中实现Play()方法方法n在在CSon类中实现类中实现Clear()方法方法n通过类通过类CFather的实例调用接口成员的实例调用接口成员n在在CFather类中实现属性类中实现属性xn在在CFather类中实现属性类中实现属性yn在在CFather类中实现类中实现Play()方法方法n在在CFather类中实现类中实现Clear()方法方法n 我们通过本例可以看到,在重新实现的接口映射中,将会查找继承的公有成员和显式接口成员实现,我们通过本例可以看到,在重新实现的接口映射中,将会查找继承的公有成员和显式接口成员实现,所以映射过程将会首先在派生类中查找匹配成员。如果派生类没有为重新实现接口的所有成员提供所有所以映射过程将会首先在派生类中查找匹配成员。如果派生类没有为重新实现接口的所有成员提供所有的实现,则映射过程会接着在基类中查找相应的匹配成员,所以在本例中的实现,则映射过程会接着在基类中查找相应的匹配成员,所以在本例中CSon类中的类中的x和和y属性会使用属性会使用其基类其基类CFather提供的实现。提供的实现。 疡烛湖销塑世吃劳闲裕烦死损惨郝劝豁阁良搂劫舱宣斥缩堡穿遁粉非院丝接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.1.8 接口的查询接口的查询 nclass Appnnpublic static void Main()nnMyBook mb=new MyBook ();nif( mb is Billing) /将检查类将检查类MyBook是否实现了接口是否实现了接口 Billing的成员的成员nnBilling bill=(Billing)mb;nBool status=mb.Calculate_Discount();nnelsennConsole.WriteLine(不支持接口不支持接口);nnnn程序运行结果为:计算折扣值。程序运行结果为:计算折扣值。跋焙揭毕憋素船匿文崖阶贝收填处掸早瓶枕警矣盎空纽刺试览浊嘛入写钧接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2 代代 理理 u 10.2.1 代理的概念代理的概念 u 10.2.2 代理的定义代理的定义 u 10.2.3 代理的使用代理的使用 u 10.2.4 Delegate类和类和MulticastDelegate 类类 u 10.2.5 多重代理的实现多重代理的实现 僳四瞥销搬藐畦歇鞭剔蕾勺瞳药钢竭顶情赢悯抒沛掖逾靶伟娩譬转商解退接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2 代代 理理 巾刃顶隔额走馋逃贡层斜肥眯详模屈吱骏敲劈惑综宛哀咯府嗓杀缺稗菏井接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.1 代理的概念代理的概念 n在在C/C+中,调用函数有两种方式,一种是通过函数名调用;另一种是利用函数指针中,调用函数有两种方式,一种是通过函数名调用;另一种是利用函数指针(函函数的入口地址数的入口地址)来调用函数。采用第二种函数调用方式的目的,是通过一个指向函数的指来调用函数。采用第二种函数调用方式的目的,是通过一个指向函数的指针变量针变量(存放函数指针存放函数指针)来灵活调用多个不同功能的方法。例如我们要求多个不同被积函数来灵活调用多个不同功能的方法。例如我们要求多个不同被积函数的定积分,积分的方法是一样,只是被积函数不一样,具体做法是:除了定义一系列被积的定积分,积分的方法是一样,只是被积函数不一样,具体做法是:除了定义一系列被积函数外,再定义一个求积分的公用函数,在这个函数中定义一个指向函数的指针变量,用函数外,再定义一个求积分的公用函数,在这个函数中定义一个指向函数的指针变量,用来存放函数的入口地址来存放函数的入口地址(函数指针函数指针),并在此函数中通过该指针变量指向不同的被积函数入,并在此函数中通过该指针变量指向不同的被积函数入口从而求出不同的定积分。另外,在口从而求出不同的定积分。另外,在C/C+中函数指针仅仅是一个内存地址,这个地址中函数指针仅仅是一个内存地址,这个地址不包含任何有关函数的参数、返回值以及调用约定等信息,因此它不是类型安全的。不包含任何有关函数的参数、返回值以及调用约定等信息,因此它不是类型安全的。n在在C#中,代理中,代理(delegate)的作用类似于的作用类似于C/C+中的函数指针,并且它是面向对象的和类中的函数指针,并且它是面向对象的和类型安全的。在型安全的。在C#中的方法类似于中的方法类似于C/C+中函数,所以方法在内存中也有一个入口物理地中函数,所以方法在内存中也有一个入口物理地址,它就是方法被调用的地址。方法的入口地址可以赋给址,它就是方法被调用的地址。方法的入口地址可以赋给“代理代理”,通过,通过“代理代理”调用该调用该方法,而且同一个代理可以调用多个不同的方法。所以使用代理可以在程序运行期调用所方法,而且同一个代理可以调用多个不同的方法。所以使用代理可以在程序运行期调用所需的某个方法,而不用在编译时固定指出调用什么方法。这个特性在创建一个可动态插入需的某个方法,而不用在编译时固定指出调用什么方法。这个特性在创建一个可动态插入组件的框架时十分有用,例如在一个图画程序组件的框架时十分有用,例如在一个图画程序(如如Windows附件中的图画附件中的图画)中,使用代理中,使用代理可以让用户插入一个特殊的过滤器和图象分析器,而且,用户可以创建一系列这样的过滤可以让用户插入一个特殊的过滤器和图象分析器,而且,用户可以创建一系列这样的过滤器和分析器。另外,代理也是实现器和分析器。另外,代理也是实现.NET框架中事件处理机制的基础。框架中事件处理机制的基础。埂威泥镇嫂励芬宿娩可猩谨恋狼倔田酿梨尝陪盗挠蠕砖寐兑铲巾暑平河拈接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.2 代理的定义代理的定义 n在在C#中,代理同类和接口一样,也是一种引用类型。用关键字中,代理同类和接口一样,也是一种引用类型。用关键字delegate定义一个代理,格式如下:定义一个代理,格式如下:n代码属性代码属性 修饰符修饰符 delegate 返回类型返回类型 代理名代理名(形式参数列表形式参数列表);n其中:其中:n“代码属性代码属性”用于指出有关被定义的代理的附加说明性信息。用于指出有关被定义的代理的附加说明性信息。n“修饰符修饰符”可以是可以是new和和4个访问修饰符个访问修饰符(public、protected、internal和和private):n使用使用new修饰符定义代理,则表明当前定义的代理隐藏继承来的同名代理。修饰符定义代理,则表明当前定义的代理隐藏继承来的同名代理。npublic:所有对象和类型都可以无条件地访问所定义的代理。:所有对象和类型都可以无条件地访问所定义的代理。nprotected:只允许定义代理的类及其派生类能访问该代理。:只允许定义代理的类及其派生类能访问该代理。ninternal:只有代理所属的工程项目的成员能访问该代理。:只有代理所属的工程项目的成员能访问该代理。nprivate:只有定义代理的类能访问该代理。:只有定义代理的类能访问该代理。n代理名可以是代理名可以是C#的任意合法的标识符。的任意合法的标识符。n返回类型是指代理所指向方法的返回值的类型。只有代理的标识和返回类型与代理所指向的方法的标识返回类型是指代理所指向方法的返回值的类型。只有代理的标识和返回类型与代理所指向的方法的标识和返回值类型一致时,才能成功使用该代理,所以代理的返回类型必须与它所指向的方法的返回类型一和返回值类型一致时,才能成功使用该代理,所以代理的返回类型必须与它所指向的方法的返回类型一致。致。n形式参数列表用于指出代理所指向方法的参数列表,这个列表必须与代理所指向方法的参数列表中的参形式参数列表用于指出代理所指向方法的参数列表,这个列表必须与代理所指向方法的参数列表中的参数个数及其参数类型一致,包括形参的顺序、个数和类型。如果代理所指向的方法本身没有参数,则此数个数及其参数类型一致,包括形参的顺序、个数和类型。如果代理所指向的方法本身没有参数,则此处代理定义中也不要任何参数。处代理定义中也不要任何参数。 咀揽贷仆粪凰枕间咐政坛现馁澈练纶彦钉贿紫锈久属羽鸟橱茄脖像醒一冰接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.2 代理的定义代理的定义 n 我们可以在任何全局范围内我们可以在任何全局范围内(某个命名空间某个命名空间)、一个类或其他类型的内部定义代理。但由于、一个类或其他类型的内部定义代理。但由于代理是隐式封闭的,所以不可以派生新的代理。代理是隐式封闭的,所以不可以派生新的代理。n例如,在一个类中定义一个代理例如,在一个类中定义一个代理MyDelegate:nclass MyClassnn /类的其他成员定义类的其他成员定义npublic delegate void MyDelegate(string s);nn此代理定义中此代理定义中void表示代理指向的方法不返回任何值,参数表示代理指向的方法不返回任何值,参数string s表示代理指向的方法表示代理指向的方法将接受一个字符串。将接受一个字符串。n从代理的定义格式可以看出,它和定义一个抽象类的抽象方法的格式相像,只是定义的关从代理的定义格式可以看出,它和定义一个抽象类的抽象方法的格式相像,只是定义的关键字不同而已,把键字不同而已,把abstract现换成了现换成了delegate。n在在C#中,当定义了一个代理,如中,当定义了一个代理,如MyDelegate,则,则C#编译器就会根据代理的定义语句自编译器就会根据代理的定义语句自动生成一个从动生成一个从System. MulticastDelegate类类(将在后面介绍将在后面介绍)派生的类派生的类MyDelegate。MyDelegate类有一个构造函数类有一个构造函数(在本处为在本处为public MyDelegate(object target, Int32 methodPtr);),C#自动生成的代码示意如下:自动生成的代码示意如下:高瘸屎琳救贰讶咱谭呜对熬数股烟梢嗜二衡犯裂票溅侥粘熊攀相式胚炳条接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.2 代理的定义代理的定义 npublic class MyDelegate : System.MulticastDelegatennpublic MyDelegate(object target, Int32 methodPtr); /构造函数构造函数n/以下方法的原型与代理定义一致以下方法的原型与代理定义一致n/此方法用来调用代理实例所封装的方法此方法用来调用代理实例所封装的方法npublic void virtual Invoke(Object value, Int32 item, Int32 numItems) n/以下两方法用于执行异步调用以下两方法用于执行异步调用npublic virtual IAsyncResult BeginInvoke(nObject value, Int32 item, Int32 numItems , AsyncCallback callback , Object object) ;nPublic virtual void EndInvoke ( IasyncResult result ) n.nn在上述构造函数中有两个参数:前者在上述构造函数中有两个参数:前者target变量是调用方法的对象,如果代理对象要调用的是变量是调用方法的对象,如果代理对象要调用的是实例方法,那么实例方法,那么target就是定义实例方法的类的实例。而如果代理要调用的方法是静态方法,就是定义实例方法的类的实例。而如果代理要调用的方法是静态方法,则则target就为就为null;后者;后者methodPtr变量是代理要调用的方法,这个变量的值不能为变量是代理要调用的方法,这个变量的值不能为null值。值。由此我们可以知道代理是如何实现其代理服务的。由此我们可以知道代理是如何实现其代理服务的。无饺瑚舰残盾纂字沈幂盛暗赏近堆干羞泪眺矗译疲划祟践鞍抹硅涩想悄矣接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 n我们在本节的开头已经说明在我们在本节的开头已经说明在C/C+中,同代理最接近的概念是函数指针,不中,同代理最接近的概念是函数指针,不过代理的使用范围比函数指针的使用范围更广。函数指针只能引用静态方法,而过代理的使用范围比函数指针的使用范围更广。函数指针只能引用静态方法,而代理不仅可以引用静态方法代理不仅可以引用静态方法(静态方法是与类相关的方法静态方法是与类相关的方法),而且还可以引用对象,而且还可以引用对象的实例方法的实例方法(实例化方法是与类的对象实例相关的方法实例化方法是与类的对象实例相关的方法)。而且代理是面向对象,。而且代理是面向对象,且类型安全的。且类型安全的。n由此可知,代理的最大作用就是它可以在运行期决定调用哪个方法,并且不但可由此可知,代理的最大作用就是它可以在运行期决定调用哪个方法,并且不但可以调用对象的实例化方法,还可以调用类的静态方法。但使用代理的最重要的特以调用对象的实例化方法,还可以调用类的静态方法。但使用代理的最重要的特征是,代理只是检查要调用的方法是否与代理的标识相匹配,因为代理只能调用征是,代理只是检查要调用的方法是否与代理的标识相匹配,因为代理只能调用和其定义特征相符的方法,所以代理可以执行匿名方法调用。和其定义特征相符的方法,所以代理可以执行匿名方法调用。n 代理虽然规定了标识和返回类型,但这些都只能由方法来实现。从这一点看,代理虽然规定了标识和返回类型,但这些都只能由方法来实现。从这一点看,代理类似于接口。代理和接口都允许方法的声明和方法的实现相分离,也就是说,代理类似于接口。代理和接口都允许方法的声明和方法的实现相分离,也就是说,都是先声明方法,然后由其他对象来实现。但是代理与接口在使用上有所不同:都是先声明方法,然后由其他对象来实现。但是代理与接口在使用上有所不同:当调用单一方法或要为指定的标识和返回类型实现一些方法时,通常使用代理;当调用单一方法或要为指定的标识和返回类型实现一些方法时,通常使用代理;而接口用于声明多个带有不同的标识和返回类型的相关方法。而接口用于声明多个带有不同的标识和返回类型的相关方法。诲丫良渝嗡像笼瘴涎傻忙凿垒必悼挛栅驮寄浚步昆慌歼方哀梯钡蝎雨航巧接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 n1. 使用代理的程序结构n(1) 定义一个代理。定义一个代理。n(2) 使用使用new运算符创建一个运算符创建一个“代理对象代理对象”,同时为该代理,同时为该代理对象指出调用的方法名。这个过程又称为代理的实例化。对象指出调用的方法名。这个过程又称为代理的实例化。n(3) 应用程序中像使用方法名一样使用应用程序中像使用方法名一样使用“代理对象代理对象”来调用来调用方法。方法。敖云蛊菲篡冻沈粒矫里寂位耙统泌午听炳瞒陵蹿摘灭区蓑迈予篡跑裤数匝接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 n2. 代理的实例化n在在C#中代理虽然可以在类中定义,看作是类的一个成员,但它本身是一个类,中代理虽然可以在类中定义,看作是类的一个成员,但它本身是一个类,所以使用代理前也必须实例化,即创建一个代理对象,同创建类对象一样用所以使用代理前也必须实例化,即创建一个代理对象,同创建类对象一样用new运算符创建相应的代理对象。按理说我们在创建代理实例时,必须提供上运算符创建相应的代理对象。按理说我们在创建代理实例时,必须提供上述创建代理对象构造函数的两个参数述创建代理对象构造函数的两个参数target和和methodPtr,但,但C#为创建代理实为创建代理实例提供了简化方法,只需提供所调用方法的完全限定名,由例提供了简化方法,只需提供所调用方法的完全限定名,由C#编译器再从中分编译器再从中分解出调用方法的对象和调用的方法。有以下两种情况:解出调用方法的对象和调用的方法。有以下两种情况:l当代理实例调用的是实例方法时,方法的完全限定名由对象名加上点运算符再加当代理实例调用的是实例方法时,方法的完全限定名由对象名加上点运算符再加上方法名:上方法名:n对象名对象名. 实例方法名实例方法名l当代理实例引用的是静态方法时,方法的完全限定名由类名加上点运算符再加上当代理实例引用的是静态方法时,方法的完全限定名由类名加上点运算符再加上方法名:方法名:n类名类名. 静态方法名静态方法名胰眠惺魄折题赁邪朱嫩厄淫疑令腻喘松恭决锤互趾踩遏逊务录君圃衅租哟接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 n【例【例10.11】代理的使用。】代理的使用。nusing System;ndelegate int MyDelegate(int x); /定义一个代理定义一个代理npublic class MyClass /定义一个类定义一个类nnpublic static int StaticMethod(int x) /静态方法静态方法nnConsole.WriteLine(类的静态方法:类的静态方法:0, x);nreturn 0;nnpublic int InstanceMethod(int x) /实例方法实例方法nnConsole.WriteLine(类的实例方法:类的实例方法:0, x);nreturn 0;nnnpublic class Appnnpublic static void Main()nnMyClass c=new MyClass (); /创建一个类创建一个类MyClass的对象的对象nMyDelegate d1=new MyDelegate (c. InstanceMethod);n/创建代理对象创建代理对象d1,调用实例方法调用实例方法nMyDelegate d2=new MyDelegate (MyClass. StaticMethod);n/创建代理对象创建代理对象d2,调用静态方法调用静态方法拌织腥蔼初夜此溜堆跺琼姓叛祁缚疑喜返圾抖兢撅忘堤东揍训看掌燃也予接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 nd1(6666);nd2(8888);nnn运行结果:运行结果:n类的实例方法:类的实例方法:6666n类的静态方法:类的静态方法:8888n在本例的开始定义了一个代理,注意这个代理不属于任何一个类,而且也没有定义在任何的命名空间内,在本例的开始定义了一个代理,注意这个代理不属于任何一个类,而且也没有定义在任何的命名空间内,所以这样定义的代理属于所以这样定义的代理属于C#默认的命名空间默认的命名空间全局命名空间全局命名空间Global Namespace。n使用代理调用实例方法要用使用代理调用实例方法要用3条语句:首先创建一个对象实例,再由对象名和实例方法名组成参数创建条语句:首先创建一个对象实例,再由对象名和实例方法名组成参数创建一个代理,最后由此代理调用这个方法。上例中的这一个代理,最后由此代理调用这个方法。上例中的这3条语句是:条语句是:nMyClass c=new MyClass (); /创建一个类创建一个类MyClass的对象的对象nMyDelegate d1=new MyDelegate (c. InstanceMethod);n/创建代理对象创建代理对象d1,调用实例方法调用实例方法nd1(6666);n使用代理调用静态方法只要用两条语句,创建一个调用静态方法的代理,参数由类名和方法名组成,然使用代理调用静态方法只要用两条语句,创建一个调用静态方法的代理,参数由类名和方法名组成,然后由此代理调用这个静态方法,上例中的这后由此代理调用这个静态方法,上例中的这2条语句是:条语句是:nMyDelegate d2=new MyDelegate (MyClass. StaticMethod); n/创建代理对象创建代理对象d2,引用静态方法引用静态方法nd2(8888);采明旱品均驹笼言开施江钦晓霍简颈第辖玫跟奇觉茵晰僻澡澎厉痒岛锗瘴接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 l在上例中,如果把代理的定义语句放在类在上例中,如果把代理的定义语句放在类MyClass中,相应代码修改如下:中,相应代码修改如下:nusing System;npublic class MyClass /定义一个类定义一个类nndelegate int MyDelegate(int x); /定义一个代理定义一个代理n /其他代码同上其他代码同上nn这种情况下,代理是类的一个成员,需要使用类名来创建代理和使用代理,所以在这种情况下,代理是类的一个成员,需要使用类名来创建代理和使用代理,所以在Main()方法中使用代理调用方法的代码也要进行相应的修改:方法中使用代理调用方法的代码也要进行相应的修改:n/使用代理调用实例方法使用代理调用实例方法nMyClass.MyDelegate d1=new MyClass.MyDelegate (c. InstanceMethod); n/使用代理,引用静态方法使用代理,引用静态方法nMyClass.MyDelegate d2=new MyClass .MyDelegate (MyClass.Static Method); 淖阿梨肖斯极宰由涸缀止势哺捻翠赫惰瓮蜡茅龄洞址除赡凳拓狗再寨哈拯接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 l如果将如果将MyClass作为作为App类的父类,则可以直接在子类中使用父类定义的代理,而不需要每次都使用类的父类,则可以直接在子类中使用父类定义的代理,而不需要每次都使用“类名类名.代理名代理名”来使用代理,代码如下:来使用代理,代码如下:nusing System;npublic class MyClass /定义一个类定义一个类nn delegate int MyDelegate(int x); /定义一个代理定义一个代理n public int InstanceMethod(int x)nnConsole.WriteLine(类的实例方法:类的实例方法:0, x);nreturn 0;nnnpublic class App: MyClassnnpublic static void Main()nnMyClass c=new MyClass (); /创建一个类创建一个类MyClass的对象的对象nMyDelegate d1=new MyDelegate (c. InstanceMethod);n/创建代理创建代理d1,不需用类名,不需用类名n/使用代理,代理所要调用的方法名属性使用代理,代理所要调用的方法名属性(代理要调用的方法名代理要调用的方法名)nConsole.WriteLine(Method:0, d1.Method); n/使用代理,代理所要调用的对象名称属性使用代理,代理所要调用的对象名称属性(请求代理的对象所属的类或代理调用方法所属的类请求代理的对象所属的类或代理调用方法所属的类)nConsole.WriteLine(Target:0, d1.Target); nn潜里芬舶逾刨吴贴箍级仲煽夫伦躇棒恤监柳韦扮茵侯董覆壁俱劈农衫餐产接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.3 代理的使用代理的使用 n程序运行结果为:程序运行结果为:nMethod: Int32 InstanceMethod(Int32)nTarget: MyClassn在本例的最后两个语句行,使用了在本例的最后两个语句行,使用了System命名空间的命名空间的Delegate类类(该类是所有该类是所有构成代理的类的基类构成代理的类的基类)的两个属性的两个属性Method和和Target。从上述运行结果,属性。从上述运行结果,属性Method将给出方法的返回类型、方法名称和方法的参数列表;属性将给出方法的返回类型、方法名称和方法的参数列表;属性Target给给出对象所属的类。出对象所属的类。l在创建代理实例时,我们也可以把其他的代理实例作为参数。使用这种方式创建在创建代理实例时,我们也可以把其他的代理实例作为参数。使用这种方式创建的代理实例与作为参数的代理实例将引用相同的方法,所以这两种方式创建的代的代理实例与作为参数的代理实例将引用相同的方法,所以这两种方式创建的代理类型所定义的方法原型必须一致。这种方式创建代理实例的代码是:理类型所定义的方法原型必须一致。这种方式创建代理实例的代码是:nMyDelegate d2=new MyDelegate(d1);l代理只能用于调用方法,不能调用属性、索引器、自定义运算符、构造函数和析代理只能用于调用方法,不能调用属性、索引器、自定义运算符、构造函数和析构函数。构函数。宿象浚募苹赋屉裁炭觉悠农玖盗蝗衷索钝焙纬绥报美烘焙孺幢不粹扭影桩接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 n由前述已知,所有的代理类都是从由前述已知,所有的代理类都是从MulticastDelegate类派生的,而类派生的,而MulticastDelegate类又是从类又是从Delegate类派生的,它们都位于类派生的,它们都位于System命名空命名空间下。间下。n在在C#中一般的代理实例中一般的代理实例(指一个代理仅可以调用一个方法指一个代理仅可以调用一个方法)被默认为被默认为Delegate类类的对象,所以我们通常使用的对象,所以我们通常使用delegate关键字来定义代理,利用关键字来定义代理,利用new运算符来创运算符来创建代理实例,然后使用建代理实例,然后使用Delegate类的方法和属性管理代理实例,它的定义原型类的方法和属性管理代理实例,它的定义原型为:为:npublic abstract class Delegate : ICloneable, ISerializablen而而MulticastDelegate类是用来支持多重代理的,其调用列表中可以拥有多个类是用来支持多重代理的,其调用列表中可以拥有多个方法的代理。它的定义原型为:方法的代理。它的定义原型为:npublic abstract class MulticastDelegate : Delegaten多重代理是指将一组代理组成一个集合,由多重代理是指将一组代理组成一个集合,由MulticastDelegate类的一个对象类的一个对象来管理这个代理集合,利用这个代理集合执行多个方法,这个功能叫多播,我们来管理这个代理集合,利用这个代理集合执行多个方法,这个功能叫多播,我们将在下一节介绍如何使用多播。在本节我们只介绍将在下一节介绍如何使用多播。在本节我们只介绍C#中是如何由中是如何由MulticastDelegate类来实现多重代理类来实现多重代理(多多 播播)的。的。嫁撵缮绞票府舅洽称迹蟹列殷每弯佳汪巷兴促毡儿嘱脏葱窒尹晶涪砧闸笋接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 n1. Delegate类n Delegate类中有两个公共只读属性:类中有两个公共只读属性:lMethod属性:该属性用于获得代理实例要调用的静态方法名。可用以属性:该属性用于获得代理实例要调用的静态方法名。可用以下代码获得代理对象所调用的方法名:下代码获得代理对象所调用的方法名:nstring MethodName=delegate.Method.Name ;lTarget属性:该属性获得类实例,当前代理将对其调用实例方法。如果属性:该属性获得类实例,当前代理将对其调用实例方法。如果代理调用的是静态方法,则该属性为代理调用的是静态方法,则该属性为null。以下代码可用于返回代理实。以下代码可用于返回代理实例所调用的方法的类型:例所调用的方法的类型: nstring objType=delegate.Target.GetType();毅客疏篙汇驰残弃猪阜雾书陪入牧述媒卜乃袄痪腐渝奏漫鞋咸偿曹整等爹接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 n2. MulticastDelegate类nMulticastDelegate类继承类继承Delegate类中类中Method和和Target两个属性,另外还有以下成员,用于组织代理集合,实现两个属性,另外还有以下成员,用于组织代理集合,实现多重代理:多重代理:n两个重载运算符两个重载运算符=和!和!=:它们都是静态的,用于比较两个代理实例是否相等。代码为:它们都是静态的,用于比较两个代理实例是否相等。代码为:nif (delegate1=delegate2) nif (delegate1!=delegate2) nEquals方法:用于判断两个代理实例是否相等。代码为:方法:用于判断两个代理实例是否相等。代码为:nif (delegate1.Equals(delegate2) nCombineImpl方法:受保护的方法。用于把当前代理与指定的代理实例的调用链表合并起来,返回一个新的代理实例,方法:受保护的方法。用于把当前代理与指定的代理实例的调用链表合并起来,返回一个新的代理实例,该代理实例将包含一个改变后的新调用链表。代码为:该代理实例将包含一个改变后的新调用链表。代码为:n/把两个代理实例的调用链表合并成为一个新的调用链表把两个代理实例的调用链表合并成为一个新的调用链表nMyDelegate newDelegate= MyDelegate.CombineImpl(delegate1,delegate2);nRemoveImpl方法:受保护的方法。用于从多重代理的调用链表中删除一个指定的代理对象,返回一个新的代理实例,方法:受保护的方法。用于从多重代理的调用链表中删除一个指定的代理对象,返回一个新的代理实例,该代理实例将包含一个改变后的新调用链表。代码为:该代理实例将包含一个改变后的新调用链表。代码为:n/从从delegate1的调用链表中删除代理实例的调用链表中删除代理实例delegate2而成为一个新的调用链表而成为一个新的调用链表nMyDelegate newDelegate= MyDelegate.RemoveImpl(delegate1,delegate2);n用户定义的代理都是从用户定义的代理都是从MulticastDelegate类派生的,所以用户可以直接在代理实例中使用以上所述类派生的,所以用户可以直接在代理实例中使用以上所述MulticastDelegate类中的属性和方法成员。在实际编程时,我们通常不显式调用类中的属性和方法成员。在实际编程时,我们通常不显式调用CombineImpl和和RemoveImpl方法,方法,而是使用而是使用C#的重载运算符的重载运算符+=和和-=来分别调用来分别调用CombineImpl和和RemoveImpl方法方法(实际上,实际上,“+”和和“-”运算符也是运算符也是通过调用通过调用CombineImpl和和RemoveImpl方法来实现的方法来实现的),分别对应于以下,分别对应于以下 代码:代码:ndelegate1 +=delegate2; /合并两个代理实例合并两个代理实例ndelegate1 -=delegate2; /从一个多重代理中删除一个代理实例从一个多重代理中删除一个代理实例辈甸辆料恤账刃屿迢叫庞矗福肤磨敷豆粹堑化凭荐霓琳铜狗耻姥搏魄凑珠接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 nMulticastDelegate类实现多重代理类实现多重代理(多播多播)的机制是:的机制是:n(1)从从MulticastDelegate类派生的用户代理实例中含有一个调用链表,这个链表将由类派生的用户代理实例中含有一个调用链表,这个链表将由多个代理实例组成,其中每个代理实例都封装了一个相应的方法,这意味着一个代理实例多个代理实例组成,其中每个代理实例都封装了一个相应的方法,这意味着一个代理实例可以同时调用多个方法。那么代理实例是如何实现把多个代理实例连接起来构成调用链表可以同时调用多个方法。那么代理实例是如何实现把多个代理实例连接起来构成调用链表的呢?代理实例是通过私有指针变量的呢?代理实例是通过私有指针变量_prev来连接多个代理构成链表的,另外代理实例还来连接多个代理构成链表的,另外代理实例还含有含有_target和和_methodPtr两个私有指针变量,它们分别表示代理实例调用的实例和方法两个私有指针变量,它们分别表示代理实例调用的实例和方法指针,用于比较调用链表中相同位置上的两个代理实例是否相同。指针,用于比较调用链表中相同位置上的两个代理实例是否相同。 n(2)创建一个新的代理实例时,变量创建一个新的代理实例时,变量_prev被设置为被设置为null,表示链表中没有其他的代理实,表示链表中没有其他的代理实例。而当用户使用例。而当用户使用Combine方法把另一个代理实例合并到该调用链表中时,将先建立一个方法把另一个代理实例合并到该调用链表中时,将先建立一个含有含有_target和和_methodPtr值的新实例,然后把该实例的值的新实例,然后把该实例的_prev变量设为调用链表的头实例,变量设为调用链表的头实例,即从调用链表的头插入新实例。而调用链表的尾是最早加入链表的代理实例,最后返回新即从调用链表的头插入新实例。而调用链表的尾是最早加入链表的代理实例,最后返回新创建的代理实例,如图创建的代理实例,如图10.1所示。逆向构成调用链表,所以链表中的最后一个实例的所示。逆向构成调用链表,所以链表中的最后一个实例的_prev的值为的值为null。n使用使用Remove方法从调用链表中删除一个代理实例的机制与合并链表类似,也是通过重新方法从调用链表中删除一个代理实例的机制与合并链表类似,也是通过重新创建新的代理实例来实现所要求的功能。创建新的代理实例来实现所要求的功能。n(3)比较两个代理实例的调用链表是否相同的方法是:首先调用链表的顺序要相同;然比较两个代理实例的调用链表是否相同的方法是:首先调用链表的顺序要相同;然后是对应元素表示的目标对象和方法要相同后是对应元素表示的目标对象和方法要相同(即即_target和和_methodPtr两个变量的两个变量的n值值):如果方法是静态方法,则它们必须是同一个类中的同一个方法;如果方法是实例方:如果方法是静态方法,则它们必须是同一个类中的同一个方法;如果方法是实例方法,则它们必须是同一个实例的同一个方法。法,则它们必须是同一个实例的同一个方法。爹余抓芬仑娄襟堰奸虞苍晴窍奋坡暗则翔悔诱鸦邦傣科羽瞄谐素江诚以脂接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 图10.1 代理实例中的调用链表歉段畅锯永须拈舌挫弯叶库凡腊呐袒减跪亡检蚂游眶仓蒙骏懈郝阉荐蚀沸接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 n3. 代理的调用实现机制n如果假设我们定义的代理是如果假设我们定义的代理是MyDelegate,则当我们通过调用这个代理去调用方法时,则当我们通过调用这个代理去调用方法时,C#编译器将会根据我们的代理定编译器将会根据我们的代理定义语句,自动生成一个从义语句,自动生成一个从System.MulticastDelegate类派生的类类派生的类MyDelegate(上节中提到上节中提到),C#自动生成的部分代码自动生成的部分代码如下:如下:npublic class MyDelegate : System.MulticastDelegatennpublic MyDelegate(Object target, Int32 methodPtr); /构造函数构造函数n/以下方法的原型与代理定义一致以下方法的原型与代理定义一致n/此方法用来调用代理实例所封装的方法此方法用来调用代理实例所封装的方法npublic void virtual Invoke(Object value, Int32 item, Int32 numItems)nn/如果链表中存在其他的代理实例,则调用当前实例之前的代理实例如果链表中存在其他的代理实例,则调用当前实例之前的代理实例nif(_prev!=null) _prev.Invoke(value, item, numItems);n/然后再调用当前代理实例要调用的方法然后再调用当前代理实例要调用的方法nreturn _target.methodPtr(value, item, numItems);nnn从上述代码中可看出,当调用一个代理时,编译器将生成一个从上述代码中可看出,当调用一个代理时,编译器将生成一个Invoke方法,该方法就用于完成用代理实例来调用方法的方法,该方法就用于完成用代理实例来调用方法的功能。当调用一个代理时,将首先调用位于它之前的调用链表中的代理实例;而返回的是最后一个加入的功能。当调用一个代理时,将首先调用位于它之前的调用链表中的代理实例;而返回的是最后一个加入的(位于链表头部位于链表头部的的)代理实例的返回值。代理实例的返回值。n在上述调用机制中,当链表中某个实例的调用出现异常时,整个链表的调用将被终止,所以这种调用机制虽很简单,但在上述调用机制中,当链表中某个实例的调用出现异常时,整个链表的调用将被终止,所以这种调用机制虽很简单,但却不很健壮。所以在却不很健壮。所以在C#中,类中,类MulticastDelegate还提供了一个还提供了一个GetInvocationList方法,该方法通过一个数组来返回方法,该方法通过一个数组来返回调用链表中的所有代理实例。这样,我们就可以获取每个代理实例的返回值,并针对每个代理实例进行异常处理。调用链表中的所有代理实例。这样,我们就可以获取每个代理实例的返回值,并针对每个代理实例进行异常处理。GetInvocationList方法的使用见【例方法的使用见【例10.3】。】。甩宦坎赶杀斩复恼擒蹬喧嫌邑上鼠薄皆柑晶墩真洒瞬获恰邢雷皱烘撵沏宴接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 n【例【例10.12】GetInvocationList方法的使用。方法的使用。nusing System;nnamespace AppDelegatenndelegate void MyDelegate(); /定义代理定义代理nclass MyClassnnpublic static void StMethod1()nnConsole.WriteLine(调用第一个静态方法调用第一个静态方法);nnpublic static void StMethod2()nnConsole.WriteLine(调用第二个静态方法调用第二个静态方法);nnpublic void InsMethod1()nnConsole.WriteLine(调用第一个实例方法调用第一个实例方法);nnpublic void InsMethod2()nnConsole.WriteLine(调用第二个实例方法调用第二个实例方法);nnnclass Appnn/显示指定代理实例的调用链表和该代理实例对应的目标和调用的方法显示指定代理实例的调用链表和该代理实例对应的目标和调用的方法nstatic void PlayDelegate(MyDelegate d)n捎蜜盆猿鸣攻诀技瘩狮稗忙婶凹扰倒让疡朗卞牟乞暗傍耽享奴凝踩宗蛙仟接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 nConsole.WriteLine(代理代理0的调用链表为:的调用链表为:, d);nDelegate dp=d.GetInvocationList();n/建立代理建立代理d的调用链信息数组的调用链信息数组dpnfor( int i=0; idp.Length; i+)nnConsole.WriteLine(第第0个元素的目标为:个元素的目标为:1, i , ndpi.Target=null ? null : dpi.Target);n Console.WriteLine( 调用方法为:调用方法为:0, dpi.Method);nnConsole.WriteLine(当前的目标为当前的目标为0, 调用方法为调用方法为1,nd.Target=null ? null : d.Target ,d.Method);nnstatic void Main(string args)nnMyClass myobj=new MyClass ();/创建包含调用方法的实例创建包含调用方法的实例n/创建调用方法的代理实例创建调用方法的代理实例nMyDelegate myd=new MyDelegate (MyClass.StMethod1); n/向代理实例的调用链表中添加代理要调用的其他方法向代理实例的调用链表中添加代理要调用的其他方法nmyd +=new MyDelegate(MyClass.StMethod2);nmyd +=new MyDelegate(myobj.InsMethod1);nmyd +=new MyDelegate(myobj.InsMethod2);nPlayDelegate(myd); /显示代理实例显示代理实例myd的调用链表的调用链表n/通过代理实例调用的方法通过代理实例调用的方法nConsole.WriteLine();nConsole.WriteLine(代理实例调用的方法有:代理实例调用的方法有:);nmyd(); /调用多播调用多播nnn旋笼蔡疫样穷驰踩萤敦蔷绦荫玄履最撬并微亦淡字裴由局无谩南葫吹属杆接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.4 Delegate类和类和MulticastDelegate类类 n运行结果为:运行结果为:n代理代理AppDelegate. MyDelegate的调用链表为:的调用链表为:n第第0个元素的目标为:个元素的目标为:null 调用方法为:调用方法为:void StMethod1()n第第1个元素的目标为:个元素的目标为:null 调用方法为:调用方法为:void StMethod2()n第第2个元素的目标为:个元素的目标为:AppDelegate.MyClass 调用方法为:调用方法为:void InsMethod1()n第第3个元素的目标为:个元素的目标为:AppDelegate.MyClass 调用方法为:调用方法为:void InsMethod2()n当前的目标为当前的目标为AppDelegate.MyClass 调用的方法为调用的方法为void InsMethod2()n代理实例调用的方法有:代理实例调用的方法有:n第一个静态方法第一个静态方法n第二个静态方法第二个静态方法n第一个实例方法第一个实例方法n第二个实例方法第二个实例方法n在本例中,先创建了一个包含调用方法的对象实例;然后创建了一个将要调用这个对象中在本例中,先创建了一个包含调用方法的对象实例;然后创建了一个将要调用这个对象中方法的代理实例,并向该代理实例的调用链表中添加其他代理实例;最后显示代理实例的方法的代理实例,并向该代理实例的调用链表中添加其他代理实例;最后显示代理实例的调用链表,并通过代理实例来调用方法。从上述结果可见,被调用的方法顺序与代理实例调用链表,并通过代理实例来调用方法。从上述结果可见,被调用的方法顺序与代理实例的调用链表的顺序是一致的。的调用链表的顺序是一致的。浙馆疲沈雇绿棘直置润壤控照晤虞案栽暑赂伊怎馈钮懂柱辜豪掩驾獭侯重接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.5 多重代理的实现多重代理的实现 n由上所述,在由上所述,在C#语言中一个代理实例语言中一个代理实例(对象对象)含有一个调用链表,该链表含有一个调用链表,该链表可以包含多个该代理要调用的方法,这种机制用于实现一个代理实例可可以包含多个该代理要调用的方法,这种机制用于实现一个代理实例可同时调用多个方法的功能,这就是在同时调用多个方法的功能,这就是在C#中代理的一个最吸引人的特性中代理的一个最吸引人的特性多重代理多重代理(又叫多播又叫多播)。多播具有创建方法链表的能力,当调用代理时,。多播具有创建方法链表的能力,当调用代理时,所有被链接的方法都会被自动调用,即使用多播可以在一次代理调用中所有被链接的方法都会被自动调用,即使用多播可以在一次代理调用中调用方法链上的所有方法。创建这种方法链表的方法是:先实例化一个调用方法链上的所有方法。创建这种方法链表的方法是:先实例化一个代理,然后使用代理,然后使用+=运算符把方法添加到链表中;要从链表中删除一个运算符把方法添加到链表中;要从链表中删除一个方法,就使用方法,就使用-=运算符。使用多播惟一的限制是:方法链表中的方法必运算符。使用多播惟一的限制是:方法链表中的方法必须具有相同的参数,而且这些方法的返回类型必须定义为须具有相同的参数,而且这些方法的返回类型必须定义为void。鲁脐炉弯暮咒耘濒口寅夸自胎撵篷勃燥帕闷隧敢晓楷锁软冗症风更踩树份接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.5 多重代理的实现多重代理的实现 n【例【例10.13】使用多重代理】使用多重代理(多播多播)。nusing System;ndelegate void StringDelegate(ref string str); /定义一个代理定义一个代理nclass StringOps /定义一个类,进行字符串操作定义一个类,进行字符串操作nnstatic void ReplaceSpaces(ref string s) /静态方法,用连字符替换空格静态方法,用连字符替换空格nnConsole.WriteLine(用连字符替换空格用连字符替换空格);ns=s.Replace( , - ); / Replace()方法专门用于用连字符替换空格方法专门用于用连字符替换空格nnstatic void RemoveSpaces(ref string s) /静态方法,删除空格静态方法,删除空格nnstring temp= ;nint i;nConsole.WriteLine(删除空格删除空格);nfor (i=0; i=0; i-, j+)n temp +=si;ns=temp;nnpublic static void Main()n格熄砚魁榨腕政纷街锋骨钓呻狄焦都疆弦节橱儡枉痪械蜗波褪漓挑隶企蔬接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.5 多重代理的实现多重代理的实现 n/创建代理创建代理nStringDelegate strdeligate; /创建一个空值代理创建一个空值代理n/创建引用创建引用ReplaceSpaces方法的代理方法的代理nStringDelegate replacesp=new StringDelegate (ReplaceSpaces); nStringDelegate removesp=new StringDelegate (RemoveSpaces);nStringDelegate reversestr=new StringDelegate (Reverse);nstring str=I am a teacher.;n/创建多播,用于调用创建多播,用于调用ReplaceSpaces()和和Reverse()方法方法nstrdeligate = replacesp;nstrdeligate += reversestr; /用相应的代理建立方法链表用相应的代理建立方法链表(因代理代表着一个方法因代理代表着一个方法)n/调用多播调用多播nstrdeligate (ref str);n/调用多播:先调用调用多播:先调用ReplaceSpaces()方法,再调用方法,再调用Reverse()方法方法nConsole.WriteLine(操作字符串结果:操作字符串结果:+str); /显示结果显示结果nConsole.WriteLine();n/删除删除ReplaceSpaces方法,加入方法,加入RemoveSpaces方法方法nstrdeligate -= replacesp; /删除删除ReplaceSpaces方法方法nstrdeligate += removesp; /建立另一个多播,加入建立另一个多播,加入RemoveSpaces方法方法nstr=I am a teacher.; /重新设定字符串重新设定字符串n/调用多播调用多播nstrdeligate (ref str);nConsole.WriteLine(操作字符串结果:操作字符串结果:+str); /显示结果显示结果nConsole.WriteLine();nn铀抨膏瑰没稼识煽昨物法砌妄兴筏居支鳖悉砖估人措次韩草搭码盂倍译剖接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.2.5 多重代理的实现多重代理的实现 n运行结果如下:运行结果如下:n用连字符替换空格用连字符替换空格n字符串反序字符串反序n操作字符串结果:操作字符串结果: rehcaet-a-ma-I n字符串反序字符串反序n删除空格删除空格n操作字符串结果:操作字符串结果: rehcaetamaI n创建和使用多播的过程是:先把创建和使用多播的过程是:先把strdeligate的值置为的值置为replacesp的引用,然后的引用,然后使用使用+=来添加来添加reversestr引用,则当调用引用,则当调用strdeligate时,这两个方法都得到时,这两个方法都得到了调用,先把空格替换为连字符,然后再把字符串变为逆序输出。接着又把了调用,先把空格替换为连字符,然后再把字符串变为逆序输出。接着又把replacesp从方法链表中删除,并把从方法链表中删除,并把removesp加入链表。这样当再次调用加入链表。这样当再次调用strdeligate时,执行的是先删除空格,然后再把字符串变为逆序输出。在本例时,执行的是先删除空格,然后再把字符串变为逆序输出。在本例中使用中使用ref参数将会给调用者返回改变后的字符串。参数将会给调用者返回改变后的字符串。n 从上述程序代码中可见,只用一条语句从上述程序代码中可见,只用一条语句“strdeligate (ref str);”就执行了多就执行了多个方法,即多重代理可以通过一条语句执行多个方法。个方法,即多重代理可以通过一条语句执行多个方法。躁少缝霹赞邱毛销铱乏咒喇确瞻族故习爬狄凰黔烫冠缄雪驹认麓拘柞颐寨接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3 事事 件件 u 10.3.1 事件的概念事件的概念 u 10.3.2 创建事件和使用事件创建事件和使用事件 u 10.3.3 多播事件多播事件 廉簿撬欣辩蔑柞轧辕仅苦鸭丝州牧谴氛暴拧放唬鸟拜盖恰变保午缄裙冒朽接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3 事事 件件 痘墒灶瘤柱擅二互凑毖简动霞祥盔袁葛虽族藐片为琅蔷弥调唇窘次邵编模接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.1 事件的概念事件的概念 n有关有关“事件事件(Even)”的概念,对于熟悉的概念,对于熟悉Windows用户界面编程的程序用户界面编程的程序员来说并不陌生。例如,在实际应用中,我们希望在员来说并不陌生。例如,在实际应用中,我们希望在“单击某个按钮单击某个按钮”后,系统能对此后,系统能对此“单击单击”事件作出某种反应,如显示一张图表或播放一事件作出某种反应,如显示一张图表或播放一段音乐等,这就是一个典型的事件发生和事件处理过程。我们把段音乐等,这就是一个典型的事件发生和事件处理过程。我们把“单击单击”按钮的动作叫触发事件,显示图表或播放音乐叫处理按钮的动作叫触发事件,显示图表或播放音乐叫处理“单击单击”事件。事件。n在在C#语言中,事件的发生不仅仅由用户的操作驱动,如单击按钮或单击语言中,事件的发生不仅仅由用户的操作驱动,如单击按钮或单击鼠标或选择菜单等;事件还可以由程序逻辑触发,如类的某个对象的状鼠标或选择菜单等;事件还可以由程序逻辑触发,如类的某个对象的状态发生的变化,导致程序对这种变化做出某种处理等。触发事件的对象态发生的变化,导致程序对这种变化做出某种处理等。触发事件的对象称为事件发送者称为事件发送者(触发事件的对象触发事件的对象);接收事件的对象称为事件接收者;接收事件的对象称为事件接收者(处处理事件的方法理事件的方法)。毙毒贾擦埠植处吻送活淆蚌媚茄刁腕儒馋购左撵茵奖乏头罩棱功梅样钟覆接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.1 事件的概念事件的概念 n在在C#语言中,事件机制有以下特征:语言中,事件机制有以下特征:n事件和变量、方法、属性一样,也是类的一种成员。事件和变量、方法、属性一样,也是类的一种成员。使用事件机制可以实现:当类对象的某个状态发生变化使用事件机制可以实现:当类对象的某个状态发生变化(事件被触发事件被触发)时,时,系统将会通过某种系统将会通过某种“途径途径”调用类中的有关处理这个事件的方法。调用类中的有关处理这个事件的方法。n在在.NET框架中,事件是将事件发送者框架中,事件是将事件发送者(触发事件的对象触发事件的对象)与事件接收者与事件接收者(处处理事件的方法理事件的方法)相关联的一种代理类,即事件机制是通过代理来实现的。相关联的一种代理类,即事件机制是通过代理来实现的。当一个事件被触发时,由该事件的代理来通知当一个事件被触发时,由该事件的代理来通知(调用调用)处理该事件的相应处理该事件的相应方法。方法。n事件也支持多播事件也支持多播(多重代理多重代理)。一个事件发送者。一个事件发送者(触发事件的对象触发事件的对象)可以同可以同时触发多个处理事件的方法。时触发多个处理事件的方法。浓悄嘴渡寐箭窗平队萧毡悯谩俐力铸轻己琼掺蚕偿抗湛试紊卫孤轻育涟叭接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件 n我们知道在我们知道在C#中把事件作为一个代理类,并让它能根据系统的各种状态中把事件作为一个代理类,并让它能根据系统的各种状态变化自动实现跟踪和处理,即事件相当于一个或多个方法的代理。当对变化自动实现跟踪和处理,即事件相当于一个或多个方法的代理。当对象的某个状态发生了变化,代理就会被自动调用,并执行代理的方法。象的某个状态发生了变化,代理就会被自动调用,并执行代理的方法。在在C#中事件机制的工作过程如下:中事件机制的工作过程如下:n(1) 将实际应用中需通过事件机制解决的问题对象注册到相应的事件处将实际应用中需通过事件机制解决的问题对象注册到相应的事件处理程序上,表示今后当该对象的状态变化时,该对象有权使用它注册的理程序上,表示今后当该对象的状态变化时,该对象有权使用它注册的事件处理程序。事件处理程序。n(2) 当事件发生时,触发事件的对象代理就会调用该对象所有已注册的当事件发生时,触发事件的对象代理就会调用该对象所有已注册的事件处理程序。事件处理程序。n为了实现上述的为了实现上述的“事件机制事件机制”,C#语言创建和使用事件的语言结构描述语言创建和使用事件的语言结构描述包括以下步骤:包括以下步骤: 瞒智仙空塞掺拍娃诵坦东材歧纵馁廷渍控羊剧宦腕捕姐晾脯嘲噶易绚菱薄接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件 n1. 为事件定义一个代理n要使用事件,首先必须为它创建一个代理。为事件定义一个代理的语句格式如下:要使用事件,首先必须为它创建一个代理。为事件定义一个代理的语句格式如下:ndelegate void 代理名代理名(触发事件的对象名,触发事件的对象名, 事件参数事件参数);n其中:其中:n“代理名代理名”指的是事件处理方法对应的代理名称,即事件的代理。指的是事件处理方法对应的代理名称,即事件的代理。n事件的代理一般含有两个参数,用于指出所要代理的事件的触发者和该事件的职能:事件的代理一般含有两个参数,用于指出所要代理的事件的触发者和该事件的职能:n第一个参数是指触发事件的源,即触发事件的对象第一个参数是指触发事件的源,即触发事件的对象(事件发送者事件发送者);n第二个参数是一个类,它包含事件处理方法要使用的数据。第二个参数是一个类,它包含事件处理方法要使用的数据。n在在.NET框架的框架的System命名空间内,已有一个专门用于处理事件的代理类命名空间内,已有一个专门用于处理事件的代理类EvenHandler,它可用于处理各种事件,其原,它可用于处理各种事件,其原型代码如下:型代码如下:npublic delegate void EventHandler(Object sender , EventArgs e);n其中第一个参数是发送事件或触发事件的对象;第二个参数是描述关于被触发事件的事件信息,并且没有返回值。如果其中第一个参数是发送事件或触发事件的对象;第二个参数是描述关于被触发事件的事件信息,并且没有返回值。如果用户定义的事件不包含附加的信息,则可以直接使用上述用户定义的事件不包含附加的信息,则可以直接使用上述System空间下的空间下的EventHandler代理,而不必再重新定义一个代理,而不必再重新定义一个代理。这种做法是代理。这种做法是.NET框架中事件处理机制中所使用的方式,即如果开发者准备使用框架中事件处理机制中所使用的方式,即如果开发者准备使用.NET框架体系的上述预定义的代理框架体系的上述预定义的代理EvenHandler,则可以省去为事件创建代理的这一步。当然,用户也可以不使用这种模式的代理,完全可以自定义一个,则可以省去为事件创建代理的这一步。当然,用户也可以不使用这种模式的代理,完全可以自定义一个事件的代理,并且也可以具有任意的参数和任意的返回值。事件的代理,并且也可以具有任意的参数和任意的返回值。n例如:以下代码行为事件创建了一个代理,并表明将要代理的事件是检查赋给的字符,如果是一个特定的某个字符,则例如:以下代码行为事件创建了一个代理,并表明将要代理的事件是检查赋给的字符,如果是一个特定的某个字符,则驱动事件发生。驱动事件发生。ndelegate void CharEventHandler(Object sender, CharEventArgs e);n此代码定义了一个名为此代码定义了一个名为CharEventHandler的代理,并需要从的代理,并需要从EventArgs类派生出一个名为类派生出一个名为CharEventArgs的类。的类。 攻喳篆许蝶组颈伎篮翰口皂睛坦翱甲煎散寄突呐赚非邹阂杀榴熔氮区勒享接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件 n2. 创建一个包含事件信息的类n事件信息将作为事件参数发送给事件接收者。事件信息将作为事件参数发送给事件接收者。n 在在.NET框架的框架的System命名空间内,已为用户提供了一个事件参数类命名空间内,已为用户提供了一个事件参数类EventArgs(在上面在上面已提到,它是已提到,它是System命名空间中事件代理命名空间中事件代理EventHandler的第二个参数的第二个参数)。这个类是。这个类是.NET框架中所有事件参数的基类,用于存储所需的数据成员框架中所有事件参数的基类,用于存储所需的数据成员(其中定义的数据是将传递给事件其中定义的数据是将传递给事件处理方法的数据处理方法的数据)。它的原型代码为:。它的原型代码为:npublic class EventArgsnnpublic static readonly EventArgs Empty=new EventArgs();npublic EventArgs() ; /构造函数构造函数nn从上述代码可见,从上述代码可见,EventArgs类不包含任何实际的内容。如果用户要处理的事件不包含任类不包含任何实际的内容。如果用户要处理的事件不包含任何附加信息何附加信息(如如“单击按钮单击按钮”事件事件),则可以直接使用这个类来定义事件参数,这种做法是,则可以直接使用这个类来定义事件参数,这种做法是.NET框架中事件处理机制中所使用的方式。同样,用户也可不必从框架中事件处理机制中所使用的方式。同样,用户也可不必从EventArgs类派生自己类派生自己的事件参数类,而可以任意地定义一个事件参数类,或根本不使用事件参数。的事件参数类,而可以任意地定义一个事件参数类,或根本不使用事件参数。EventArgs类的派生类格式如下:类的派生类格式如下:揽郎呀搪迈兴成亩博紧谎狗聋术炸迈绵扫研妹亚逐碾话愤粤熏呆嘲罪身僚接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件npublic class xxxEventArgs : EventArgsnn./数据成员数据成员npublic xxxEventArgs(类型名类型名)nn./通过构造函数给数据成员赋初值通过构造函数给数据成员赋初值nnnxxxEventArgs类可以取任何名称,但编程习惯上应该以类可以取任何名称,但编程习惯上应该以EventArgs结尾,表示这种类的特征。结尾,表示这种类的特征。xxxEventArgs类将包含事件处理方法需要的所类将包含事件处理方法需要的所有数据,这些数据以后将被传递给事件处理方法。有数据,这些数据以后将被传递给事件处理方法。n例如:上述创建的事件代理例如:上述创建的事件代理CharEventHandler,它将传递一个,它将传递一个CharEventArgs类的对象,而类的对象,而CharEventArgs可以从可以从EventArgs类派生而来,类派生而来,代码如下:代码如下:npublic class CharEventArgs : EventArgsnnpublic char CurrChar; /字符变量字符变量npublic CharEventArgs(char CurrChar) /构造函数构造函数nnthis.CurrChar=CurrChar;nnn 上面给出的代码中定义了一个上面给出的代码中定义了一个CurrChar字符变量,事件处理方法的代码就可以使用这个变量的值;另外还给出了构造函数,当创建一个字符变量,事件处理方法的代码就可以使用这个变量的值;另外还给出了构造函数,当创建一个CharEventArgs类的对象时,构造函数将接受一个传递来的字符,并将该字符赋给类中的数据成员。类的对象时,构造函数将接受一个传递来的字符,并将该字符赋给类中的数据成员。是能沤挨披省申甥长跋汰邓矗拥响寅前麻粤尔呢晌汾卓萝遗萍匆沪男离烘接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n3. 创建包含事件成员的类(又称为事件类)n上述我们已创建了一个完成上述我们已创建了一个完成“事件过程事件过程”的全权代理,现在我们要创建的全权代理,现在我们要创建所谓的所谓的“事件类事件类”,它包含参与事件的事件成员,以及在条件满足时触,它包含参与事件的事件成员,以及在条件满足时触发事件的方法代码,即令方法与事件相关联。创建事件类的步骤如下:发事件的方法代码,即令方法与事件相关联。创建事件类的步骤如下:n(1) 定义事件成员定义事件成员n由于由于“事件过程事件过程”是由代理来执行的,所以对于所要发生的事件的定义是由代理来执行的,所以对于所要发生的事件的定义必须明确指定由哪个必须明确指定由哪个“代理代理”来代理来代理“这个事件这个事件”的,所以对某个事件的,所以对某个事件定义的格式中必须含有一个将代理该事件的代理,定义格式如下:定义的格式中必须含有一个将代理该事件的代理,定义格式如下:nevent 事件的代理名事件的代理名 事件名事件名n其中:其中:n事件的代理名是指上述第事件的代理名是指上述第(1)步中为事件创建的代理。步中为事件创建的代理。n事件名是要定义的事件对象的名称,它将用于为代理指明处理该事件所事件名是要定义的事件对象的名称,它将用于为代理指明处理该事件所要用到的事件处理方法。要用到的事件处理方法。淬捞简插鹰撰霓盒痹萎瘴拷铲民修配紧课秆百乾难件败孝埋豹羡吉烂砍远接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n(2)创建触发事件的方法或属性等成员创建触发事件的方法或属性等成员触发事件的方法或属性等成员用于接收触发事件的触发信息,并通过事件对象调用事件代理,并进而实现调用事件处理方法。触发事件的方法或属性等成员用于接收触发事件的触发信息,并通过事件对象调用事件代理,并进而实现调用事件处理方法。n例如:为上述检查特殊字符的事件自定义为一个事件类,代码如下:例如:为上述检查特殊字符的事件自定义为一个事件类,代码如下:nclass CharCheckernnchar curr_char;n/定义一个由定义一个由CharEventHandler代理的事件成员代理的事件成员CharTest npublic event CharEventHandler CharTest;n/触发事件的属性。接收触发事件的信息并调用相应的处理事件方法触发事件的属性。接收触发事件的信息并调用相应的处理事件方法npublic char Curr_Char nngetreturn curr_char; /返回类中数据成员返回类中数据成员curr_char的值的值nsetn nif (CharTest!=null)nn/ 创建创建CharEventArgs类的对象类的对象nCharEventArgs myeven=new CharEventArgs (value); n/通过事件成员通过事件成员CharTest调用事件代理,并由代理调用注册的处理事件的方法调用事件代理,并由代理调用注册的处理事件的方法CharTest(this, myeven); ncurr_char= myeven.CurrChar;nnnn霉馅洪灰暖祁拄爪择察丰硕宠东崔概负哎盖荆往紧披缮戳烈氮膘窃难炔莉接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n在上述事件类中,在上述事件类中,CharTest被定义为一个事件成员,所以在被定义为一个事件成员,所以在set属性段中,仅当没有对应属性段中,仅当没有对应的事件处理方法时,这个成员才会为的事件处理方法时,这个成员才会为null;而如果有事件处理方法,则创建一个;而如果有事件处理方法,则创建一个CharEventArgs类的对象,该类将包含事件处理方法需要的所有值。本例中传递给类的对象,该类将包含事件处理方法需要的所有值。本例中传递给set方方法的值被传递给法的值被传递给CharEventArgs类的构造函数类的构造函数(上面已给出上面已给出CharEventArgs类的代码类的代码),这是一个事件处理方法要使用的字符。这是一个事件处理方法要使用的字符。nCharTest(this, myeven )语句用于调用事件代理,这是通过前面定义的事件对象语句用于调用事件代理,这是通过前面定义的事件对象CharTest来实现的,其中参数来实现的,其中参数this指的是调用事件成员就是指的是调用事件成员就是CharTest事件成员本身;参事件成员本身;参数数myeven是是CharEventArgs类的对象,所以这个代理把事件对象与该事件对应的事件处类的对象,所以这个代理把事件对象与该事件对应的事件处理方法连在了一起,它们的关系如图理方法连在了一起,它们的关系如图10.2所示。所示。ncurr_char= myeven.CurrChar语句将语句将CharEventArgs类的对象类的对象myeven中包含的字符中包含的字符赋给数据成员赋给数据成员curr_char,这条语句是该事件类特有的,如果调用的事件处理方法中修改,这条语句是该事件类特有的,如果调用的事件处理方法中修改了数据,则本语句将确保该事件类获得更新后的值,这也正是了数据,则本语句将确保该事件类获得更新后的值,这也正是set属性的用途所在。属性的用途所在。 毡肉淫研茫补倦谁存卒糕躬沟锣羽涝盘置晋瓢涅爆标领遗莉份史昭螺冒容接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件图10.2 事件代理的桥梁作用纫王丫习额汉诗蔡芜行猎倔香溜明死侥男灿钟确搪科穷痢翌精弗恶喘愈矢接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n4. 创建事件处理方法n 当事件被触发后,将通过该事件的代理来调用处理该事件的事件处理方法。处理事件的方法是用事件代理的格式创建的,其格式如下:当事件被触发后,将通过该事件的代理来调用处理该事件的事件处理方法。处理事件的方法是用事件代理的格式创建的,其格式如下:nvoid 事件处理方法名事件处理方法名(Object sender, xxxEventArgs argName)nn/事件处理方法代码事件处理方法代码nn其中:其中:n“事件处理方法名事件处理方法名”是指事件发生时将被调用的方法的名称。是指事件发生时将被调用的方法的名称。n参数参数“Object sender”是指触发事件的对象。是指触发事件的对象。n参数参数“xxxEventArgs argName”是一个从是一个从EventArgs类派生的类,它包含事件处理方法要使用的值。类派生的类,它包含事件处理方法要使用的值。n“事件处理方法代码事件处理方法代码”可以是任意代码。如事件是可以是任意代码。如事件是“单击按钮单击按钮”,则这些代码将在按钮被单击时执行,比如,如果事件是,则这些代码将在按钮被单击时执行,比如,如果事件是“取消按钮取消按钮”,则事件,则事件处理方法代码将执行取消逻辑;如果是处理方法代码将执行取消逻辑;如果是“OK按钮按钮”,则这些代码将执行一些正常执行的代码。,则这些代码将执行一些正常执行的代码。n 事件处理方法可以定义在应用类中,也可以专门创建一个包含事件处理方法的类。事件处理方法可以定义在应用类中,也可以专门创建一个包含事件处理方法的类。n例如:仍用上述字符事件来说明,定义一个事件处理方法:如果输入一个字符是例如:仍用上述字符事件来说明,定义一个事件处理方法:如果输入一个字符是x,则将替换为?,这个方法虽没什么价值,但便于我们理解:,则将替换为?,这个方法虽没什么价值,但便于我们理解:nstatic void Change_X(Object source, CharEventArgs e)nnif(e.CurrChar=x | e.CurrChar=X)nnConsole.WriteLine(Change the x!);ne.CurrChar=?;nn调栈侵蓝毯源瑶闷第课瘪状吠匠令兽扑竿录贵禽胆羹副俱盎撇讣珍船勾象接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n5. 将事件处理方法和事件关联起来n最后,我们应将包含事件成员的类和包含事件处理方法的类通过代理关联起来,以便当包最后,我们应将包含事件成员的类和包含事件处理方法的类通过代理关联起来,以便当包含事件成员的类的状态发生变化时,可以准确地通过代理来调用相应的事件处理方法,这含事件成员的类的状态发生变化时,可以准确地通过代理来调用相应的事件处理方法,这一关联过程是在主程序中完成的。步骤如下:一关联过程是在主程序中完成的。步骤如下:n(1)创建事件类的对象创建事件类的对象要将事件处理方法与相应的事件关联,必须在主程序中事先创建一个事件类的对象。就上要将事件处理方法与相应的事件关联,必须在主程序中事先创建一个事件类的对象。就上述字符事件的例子而言,可以创建一个事件类的对象述字符事件的例子而言,可以创建一个事件类的对象chartester:nCharChecker chartester=new CharChecker();n(2)将事件处理方法与事件对象关联将事件处理方法与事件对象关联将事件处理方法与事件对象关联,就是我们在本节一开始提出的将事件处理方法与事件对象关联,就是我们在本节一开始提出的“注册注册”。可以把一个事。可以把一个事件与一个或多个事件处理方法相关联。当把一个事件与多个事件处理方法相关联,也叫做件与一个或多个事件处理方法相关联。当把一个事件与多个事件处理方法相关联,也叫做“多播多播”,具体实现方法与代理的多播一样,可以用,具体实现方法与代理的多播一样,可以用+=运算符来实现。当然也可以使用运算符来实现。当然也可以使用-=运算符删除与一个事件相关联的事件处理方法。实现关联的语句格式如下:运算符删除与一个事件相关联的事件处理方法。实现关联的语句格式如下:n事件类对象名事件类对象名. 事件类中定义的事件成员事件类中定义的事件成员 +=new 事件代理名事件代理名(事件处理方法名列表事件处理方法名列表);n例如在字符事件的使用时,建立事件与事件处理方法的关联例如在字符事件的使用时,建立事件与事件处理方法的关联(注册注册)语句可以是:语句可以是:nchartester. CharTest +=new CharEventHandler(Change_X);雀善息诊啃悠王氨喊彦心衬棍沛帆膳迅满眷柯伪驭检皮跳咬卡卜豆恶么珊接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n6. 组成一个完整的创建事件和使用事件的程序n 以下将上述的以下将上述的“字符事件字符事件”创建和使用的全过程代码给出,以便读者领会创建和使用的全过程代码给出,以便读者领会C#语言中的语言中的“事件机制事件机制”实现的应用:实现的应用:nusing System;ndelegate void CharEventHandler(Object sender, CharEventArgs e);n/创建一个事件代理创建一个事件代理npublic class CharEventArgs : EventArgs /创建一个包含事件所需信息创建一个包含事件所需信息(数据数据)的类的类nnpublic char CurrChar; /字符变量字符变量npublic CharEventArgs(char CurrChar) /构造函数构造函数nnthis.CurrChar=CurrChar;nnnclass CharChecker /定义事件类定义事件类nnchar curr_char;n/定义一个由定义一个由CharEventHandler事件成员事件成员CharTest npublic event CharEventHandler CharTest; npublic char Curr_Char n/触发事件的属性。接收触发事件的信息并调用处理事件的方法触发事件的属性。接收触发事件的信息并调用处理事件的方法nngetreturn curr_char; /返回类中数据成员返回类中数据成员curr_char的值的值nsetn nif (CharTest!=null)n蚁仆力刑瓮州茫彩鞠惫棺伴贩溪捎又跑缝供容雌减辞苇蜂谬杜凹秒徊恒蟹接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n/ 创建创建CharEventArgs类的对象类的对象nCharEventArgs myeven =new CharEventArgs (value); n/通过事件成员通过事件成员CharTest调用事件代理,并由代理调用注册的事件处理方法调用事件代理,并由代理调用注册的事件处理方法CharTest(this, myeven); ncurr_char= myeven.CurrChar; nnnnnclass AppEventnnstatic void Main()nnCharChecker chartester=new CharChecker();n/创建事件类创建事件类CharChecker的对象的对象n/通过事件代理向事件成员注册事件处理方法通过事件代理向事件成员注册事件处理方法nchartester. CharTest +=new CharEventHandler(Change_X);n/由事件对象由事件对象chartester通过事件属性通过事件属性Curr_Char的的set访问函数来触发事件访问函数来触发事件nchartester.Curr_Char =a; n/ 通过事件属性通过事件属性Curr_Char的的get访问函数返回事件处理结果访问函数返回事件处理结果nConsole.WriteLine(事件处理结果:事件处理结果:0, chartester.Curr_Char ); nchartester.Curr_Char =b; n Console.WriteLine(事件处理结果:事件处理结果:0, chartester.Curr_Char );nchartester.Curr_Char =x; n Console.WriteLine(0, chartester.Curr_Char );n Console.WriteLine();n桥答压泛幽雕猾申眠曾滨凯撒剿腥壳千兼祸击亩措喊懊敌藤象封坐锅真滤接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件nstatic void Change_X(Object source, CharEventArgs e)n/事件处理方法事件处理方法nnif(e.CurrChar=x | e.CurrChar=X)nnConsole.Write(触发的字符是触发的字符是x,);nConsole.Write(把把x替为:替为:);ne.CurrChar=?;nnelse nConsole.Write(触发的字符不是触发的字符不是x,);nnn程序运行结果:程序运行结果:n触发的字符不是触发的字符不是 x,事件处理的结果:,事件处理的结果:an触发的字符不是触发的字符不是 x,事件处理的结果:,事件处理的结果:bn触发的字符是触发的字符是 x,把,把x替为:替为:?n以下再举一例以加深对事件的理解。以下再举一例以加深对事件的理解。租捍婿弥峻漓馅邮欲技剃茹屹徒拭祖无涎屿贫碍闺屡阅措秉辣偷粗酱恫轧接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件n【例【例10.14】一个】一个E-mail事件的处理程序。当收到一个事件的处理程序。当收到一个E-mail消息时,消息时,程序就把这条消息转发给传真机或直接显示在屏幕上。程序就把这条消息转发给传真机或直接显示在屏幕上。n在此例中,我们根据事件发生的过程,先创建一个在此例中,我们根据事件发生的过程,先创建一个E-mail管理器,即一管理器,即一个个MailManager类,该类的将包括一个事件成员类,该类的将包括一个事件成员MailMag、一个事件、一个事件代理成员代理成员MailMagEventHandler和一个接收发来的和一个接收发来的E-mail信息的触发信息的触发事件的方法。还要建一个包含事件处理方法的类事件的方法。还要建一个包含事件处理方法的类Fax。整个程序完成的。整个程序完成的功能是:当功能是:当MailManager管理器收到管理器收到E-mail消息时,它就触发消息时,它就触发 MailMag事件,并把事件,并把E-mail消息转发给注册到该事件上的消息转发给注册到该事件上的Fax对象。程对象。程序结构如图序结构如图10.3所示。所示。佰伯忻农括鹤蓬雇嗣阁健咀灾揽铝诧狗橡介奇郭屏园思津碾郑省和垢值骚接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.2 创建事件和使用事件创建事件和使用事件图10.3 程序结构示意图笆浇逼调型膳立绥荧字鸦掖崎球啪危枷教心壶拓淄炕品衔抉福瑰黔秃泄肯接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.3 多播事件多播事件 n事件可以多播,这一特性可以使多个对象响事件可以多播,这一特性可以使多个对象响应事件信息;还可以通过多点传送应事件信息;还可以通过多点传送(multicasting)为同一个事件指定多个事件为同一个事件指定多个事件处理方法处理方法(新加入的处理程序必须有同样的格新加入的处理程序必须有同样的格式式)。要加入其他的事件处理程序,可以使用。要加入其他的事件处理程序,可以使用上节中介绍的上节中介绍的+=运算符。下面举例说明:运算符。下面举例说明:藤铲舜匝姚曳宦谢咐播主屑黑俞升刺肝徐港谱骑庄丁恋绞参防抨详帅挤殷接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.3 多播事件多播事件 n【例【例10.15】使用多个事件处理程序将前面的字符事件修改如下。】使用多个事件处理程序将前面的字符事件修改如下。nusing System;ndelegate void CharEventHandler(Object sender, CharEventArgs e);npublic class CharEventArgs : EventArgsnnpublic char CurrChar; /字符变量字符变量npublic CharEventArgs(char CurrChar) /构造函数构造函数nnthis.CurrChar=CurrChar;nnnclass CharChecker /定义事件类定义事件类nnchar curr_char;n/定义一个由定义一个由CharEventHandler事件成员事件成员CharTest npublic event CharEventHandler CharTest; npublic char Curr_Char/触发事件的属性。接收触发事件的信息并调用处理事件的方法触发事件的属性。接收触发事件的信息并调用处理事件的方法nngetreturn curr_char; /返回类中数据成员返回类中数据成员curr_char的值的值nsetn nif (CharTest!=null)n哲谭逻枢甚榔第栈闺巴锅詹普尸渤颈泰堪唐叔技最砧姥匝佰耶挞擞述坝人接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.3 多播事件多播事件 nCharEventArgs args=new CharEventArgs (value);/ 创建创建nCharEventArgs类的对象类的对象n/通过事件成员通过事件成员CharTest调用事件代理,并由代理调用注册的事件处理方法调用事件代理,并由代理调用注册的事件处理方法nCharTest(this, args); ncurr_char=args.CurrChar; nnnnnclass AppEventnnstatic void Main()nnCharChecker chartester=new CharChecker();/创建事件类创建事件类nCharChecker的对象的对象n/通过事件代理向事件成员注册事件处理方法通过事件代理向事件成员注册事件处理方法nchartester. CharTest +=new CharEventHandler(Change_X);n/将新的事件处理方法加入到事件处理调用列表中,当赋给字母时,两个事件处理方法将新的事件处理方法加入到事件处理调用列表中,当赋给字母时,两个事件处理方法n都执行都执行nchartester. CharTest +=new CharEventHandler(Change_Y); n/由事件对象由事件对象chartester通过事件属性通过事件属性Curr_Char的的set访问函数来触发事件访问函数来触发事件nchartester.Curr_Char =a; n/ 通过事件属性通过事件属性Curr_Char的的get访问函数返回事件处理结果访问函数返回事件处理结果nConsole.WriteLine(事件处理结果:事件处理结果:0, chartester.Curr_Char ); nchartester.Curr_Char =x; nConsole.WriteLine (把把x替为:替为:0 , chartester.Curr_Char);nchartester.Curr_Char =b; nConsole.WriteLine(事件处理结果:事件处理结果:0, chartester.Curr_Char );nchartester.Curr_Char =y; nConsole.WriteLine (把把y替为:替为:0 , chartester.Curr_Char);n掌表啤沧舷矛暂蹈粪境攒或症笆瘴嚎脂歌棕示肯题亚耶适愚舆纸窗矣佰杉接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.3 多播事件多播事件 nstatic void Change_X(Object source, CharEventArgs e)n/事件处理方法事件处理方法nnif(e.CurrChar=x | e.CurrChar=X)nnConsole.Write(触发的字符是触发的字符是x,);ne.CurrChar=?;nnelse n Console.Write(触发的字符不是触发的字符不是x,);nn/新的事件处理方法,其格式与要求的相同,返回类型为新的事件处理方法,其格式与要求的相同,返回类型为voidnstatic void Change_Y(Object source, CharEventArgs e)nnif(e.CurrChar=y | e.CurrChar=Y)nnConsole.Write(触发的字符是触发的字符是y,);ne.CurrChar=#;nnelse nConsole.Write(触发的字符不是触发的字符不是y,);nn贡峨撩赫挨腥兆庇雕闲勤两饿身维艰胰锣酣碳独绅延驹稻刊循粟婉妊退硝接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/10.3.3 多播事件多播事件 n程序运行结果:程序运行结果:n触发的字符不是触发的字符不是x, 触发的字符不是触发的字符不是y, 事件处理结果:事件处理结果:an触发的字符是触发的字符是x, 触发的字符不是触发的字符不是y, 把把x替为:替为:?n触发的字符不是触发的字符不是x, 触发的字符不是触发的字符不是y, 事件处理结果:事件处理结果:bn触发的字符不是触发的字符不是x, 触发的字符是触发的字符是y, 事件处理结果:事件处理结果:#苔蒲绰户敌焦渍萝农耸桶斟恬涨麓沏沛搓竹陵笨翱鲜僧魁踪下墩叼戏莉萨接口代理和事件接口代理和事件http:/www.wenyuan.com.cn/webnew/Q & A?Thanks!哦甩硅祁椿跌咋鬼诞冈锋贾棵卤丸剃擞戒猜划詹淖族耻捡悟涧红袁黍慌陈接口代理和事件接口代理和事件
收藏 下载该资源
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号