资源描述
http:/ http:/ n前面已经介绍了有关面向对象程序设计的基本实现技能,本章将介绍一些面向对象程序设计的高级技术:接口、代理和事件。接口和代理都属于C#语言的引用数据类型,而事件是C#语言新增加的一个成员。由于事件和代理有很密切的关系,所以把事件也放在本章介绍。n本章教学目的:n了解接口和类的区别n掌握接口的定义,实现和使用n掌握创建和使用代理的方法n掌握Delegate类和MulticastDelegate类实现多重代理的方法n掌握创建和使用事件的方法 http:/ n10.1 接 口n10.2 代 理 n10.3 事 件 http:/ 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:/ n第9章中提到,C#只允许单继承机制。而如果我们希望一个子类继承两个或两个以上更多的父类时,C#语言是不支持的,即C#不允许利用类进行多重继承。但在C#语言中,“多重继承”的功能是通过“接口(Interface)”技术来模拟实现的。 http:/ n在C#中需要实现多重继承时,就要使用接口技术。接口具有类似于抽象类的地位,它只具有“被继承”特性,所以接口也像抽象类一样,是一个最高层次的“基类”。但抽象类只能实现单继承,而接口可实现多继承。因此,在C#中,接口和抽象类既有相同的继承特性(最高层“基类”),又有不同的实现机制。n抽象类指的是至少包含一个抽象方法的类,而抽象方法指的是被继承时,必须被重写的方法。而接口是另一种类似于抽象类的引用类型,它主要用来声明要定义的类中将包含哪些功能(方法、属性、索引或事件),但不包含这些功能的实例化代码(同抽象类),只在“继承”(通常,在接口技术中称为“实现”,以便与类中的“继承”有所区别)时才实例化这些功能的代码(也同抽象类)。换句话说,接口只是定义了类必须做什么,而不是怎样做。n一旦定义了一个接口,许多类都可以实现它。所谓“实现接口”就是意味着某个将要使用这个接口的类,必须为该接口所定义的方法、属性、索引或事件提供实体(实现的代码)。 http:/ n所以说接口类似于抽象类,但它与类之间有以下区别:l接口只提供类所需实现的方法、属性、索引或事件的格式或约定,不提供任何相应的功能代码。具体的功能代码由继承(使用)该接口的类或结构来实现,这叫作“接口实现”。l接口中只包含方法、属性、索引或事件,而不包含任何数据成员、构造函数、析构函数和静态函数,而且接口中的所有成员都被视为公有,不能有任何访问修饰符。l要实现接口的类必须实现接口中的所有成员,即当一个接口或类从其他接口继承时,它将继承它的基接口中的所有成员。而抽象类则可以根据需要重载部分或全部抽象成员。l接口允许多重继承。一个接口可从多个基接口继承,并包含这些基接口继承树上的所有基接口;一个类可以从多个基接口继承;但一个类最多只能有一个直接父类。 http:/ n在实际应用中是使用接口还是抽象类为组件提供多态性,一般从以下几点考虑:n如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。 n如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。 n如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。 n如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。 http:/ n 1. 接口定义n接口定义的语句格式为:n 代码属性 修饰符 interface 接口名:基接口列表n n /接口成员定义体n http:/ n 2. 接口成员声明n接口成员包括从基接口继承的成员以及接口自身定义的成员。接口成员可以是方法、属性、索引和事件,但不能有常数、运算符、构造函数、析构函数、类型和静态成员。因为接口只具有“被继承”的特性,所以默认时,所有接口成员只具有public特性,接口成员的声明中不能含有任何其他修饰符。 http:/ n (1)接口的方法成员声明格式如下:n 代码属性 new 返回值类型 方法名(参数1,参数2, .);n接口中只能提供方法的格式声明,而不能包含方法的实现,所以接口方法的声明总是以分号结束。n用户可以使用new修饰符在派生的接口中隐藏基接口的同名方法成员,其作用与类中new修饰符的作用相同。例如:n interface IAn n void Math();n n interface IB: IA /接口IB继承接口IAn n new void Math(); /如果不加new修饰符,将会有警告。加上new就可消除n http:/ n【例10.1】定义一个接口DataSeries,任何类使用该接口可以产生一系列数字。n public interface DataSeriesn n int getNext(); /返回数字系列中的下一个数字n void reset(); /重新开始n void setStart(int x); /设置初始值n http:/ n (2)接口的属性成员声明格式如下:n 代码属性 new 属性类型 属性名get;和/或set;n同理,接口中的属性成员不能包含实现,所以只能以分号结束。在接口属性成员中同样也可以使用new修饰符来隐藏从基接口继承的同名属性成员。接口属性成员的访问方式有只读、只写和可读写3种,如表10.1所示。 http:/ 访问方式描 述get;表示只读属性,即只能读取该属性的值set;表示只写属性,即只能对属性进行赋值get; set;表示可读写属性,即可读取属性的值也可以对它进行赋值表10.1 接口属性的访问方式 http:/ n【例10.2】重写接口DataSeries,通过类MyThree实现接口,同时使用接口属性来获取数列中的下一个元素。n using System;n public interface DataSeries /接口n n int Next /接口的一个属性n n get; /返回数列的下一个数字n set; /设置下一个数字n n n class MyThree: DataSeries /实现接口DataSeriesn n int x;n public MyThree()x=0; /构造函数n public int Next /实现接口属性:获取或设置值n n get x+=3; return x;n set x=value; n n n class App /应用类n n public static void Main()n n MyThree ob=new MyThree ();n for (int i=0; i3; i+) /通过属性访问接口n Console.WriteLine(Next value is + ob.Next);n Console.WriteLine(nStarting at 100);n ob.Next=100;n for (int i=0; i3; i+) /通过属性访问接口n Console.WriteLine(Next value is + ob.Next);n n http:/ n程序输出结果如下:n Next value is 3n Next value is 6n Next value is 9n Starting at 100n Next value is 103n Next value is 106n Next value is 109 http:/ n (3)接口的索引器成员声明格式如下:n 代码属性 new 数据类型 this 索引器参数类型 参数名 get; 和/或set;n此格式中的代码属性和new修饰符的用法和作用,与接口的方法成员和属性成员声明的含义完全一样。n声明中的“数据类型”是指索引器引入的元素类型,接口声明中的索引器成员只能用来指定索引器的访问方式,同表10.1。同样不允许在索引器参数上使用out和ref关键字。n例如:n interface IAn n n name(yang yan ) /附加信息(即属性说明)n int this int index ger; set; /索引器定义 n http:/ n【例10.3】以下是接口DataSeries的另一个版本,其中添加了返回第i个元素的只读索引。n using System;n public interface DataSeries /接口n n int Next get; set; /接口的一个属性n int thisint index /接口的一个索引,一个只读索引n n get; /返回数列中的指定数字n n n class MyThree: DataSeries /实现接口DataSeriesn n int x;n public MyThree()x=0; /构造函数n public int Next /接口属性的实现。获取或设置值n n getx+=3; return x;n setx=value;n n public int thisint index /实现索引n n getn n x=0; n for(int i=0; iindex; i+)n x+=3;n return x;n n http:/ n n class App /应用类n n public static void Main()n n MyThree ob=new MyThree ();n for (int i=0; i3; i+) /通过属性访问接口n Console.WriteLine(Next value is + ob.Next);n Console.WriteLine(Starting at 100);n ob.Next=100;n for (int i=0; i3; i+) /通过属性访问接口n Console.WriteLine(Next value is + ob.Next); n n Console.WriteLine(Resetting to 0);n ob.Next=0;n for (int i=0; i3; i+) /通过索引访问接口n Console.WriteLine(Next value is + ob.Next);n n http:/ n程序输出结果如下:n Next value is 3n Next value is 6n Next value is 9n Starting at 100n Next value is 103n Next value is 106n Next value is 109n Resetting to 0 n Next value is 3n Next value is 6n Next value is 9 http:/ n (4)接口的事件成员声明有关“事件”在本章的最后一节介绍,本处只给出接口事件的声明格式:n 代码属性 new event 事件代理名 事件名;n事件声明中的代码属性和new修饰符的用法和作用,与接口的方法成员和属性成员的声明含义完全一样。n例如:n Interface IAn n /其他成员的定义n event Click MyEvent;n http:/ n以上我们只是定义了一个接口,而在接口中声明的方法、属性、索引或事件的真正实现是由类来完成的。所以说,一旦定义了接口,一个或更多的类就可以以不同的方式来实现该接口中的功能,并且每个类必须实现该接口中所定义的所有方法、属性、索引或事件。即一个接口可以由多个类来实现,而在一个类中也可以实现一个或多个接口。n实现接口的方式与继承相同,即将接口放在类名的后面,中间用冒号隔开。实现接口的语句格式:n class 类名:接口名列表n n /类实体n n 其中接口名是指该类所要实现的接口的名称。 http:/ n 1. 一个类实现(使用)一个接口。n【例10.4】创建一个类MyThree,实现【例10.1】中定义的接口DataSeries,用于生成一系列公差为3的数列。n using System;n public interface DataSeries /接口DataSeriesn n int getNext(); /返回数字系列中的下一个数字n void reset(); /重新开始n void setStart(int x); /设置初始值n n class MyThree: DataSeries /实现接口DataSeries的类n n int start; n int value;n public MyThree() /构造函数n n start=0;n value=0;n n public int getNext() /实现接口DataSeries中的方法getNext()n n value+=3;n return value;n http:/ n public void reset() /实现接口DataSeries中的方法reset()n n start=0;n value=0;n n public void setStart(int x) /实现接口DataSeries中的方法setStart()n n start=x;n value=x;n n n class AppDataSeries /应用类 n n public static void Main()n n MyThree ob=new MyThree ();n for (int i=0; i3; i+) /循环输出3个数字n Console.WriteLine(Next value is +ob.getNext();n Console.WriteLine (Resstting); /重新开始n ob.reset();n for (int i=0; i3; i+) /循环输出3个数字n Console.WriteLine(Next value is +ob.getNext();n Console.WriteLine (Starting at 100); /从100重新开始n ob.setStart(100); n for (int i=0; i3; i+) /循环输出3个数字n Console.WriteLine(Next value is +ob.getNext();n n http:/ n程序的运行结果:n Next value is 3n Next value is 6n Next value is 9n Resettingn Next value is 3n Next value is 6n Next value is 9n Starting at 100n Next value is 103n Next value is 106n Next value is 109 n可见,要实现接口,必须在类名后包括接口,然后提供接口的每一个成员的实现。注意观察上例中接口成员和类中相应的实现的访问类型的写法,在接口成员的声明中不需要任何访问修饰符,而在类中相应接口成员实现定义中则都用public修饰符。 http:/ n类除了可以用来实现接口外,还可以定义它自己的额外成员,如我们在MyThree类中再添加一个方法getPrevious(),用于返回前一个数字的值。n class MyThree: DataSeries /实现接口DataSeries,并添加getPrevious()方法n n int start;n int value;n int prev;n public MyThree() /构造函数n n start=0;n value=0;n prev= -3; n n public int getNext() /实现接口DataSeries中的方法getNext()n n prev=value;n value+=3; http:/ n return value;n n public void reset() /实现接口DataSeries中的方法reset()n n start=0;n value=0;n prev=-3;n n public void setStart(int x) /实现接口DataSeries中的方法setStart()n n start=x;n value=x; n prev=x-3;n n int getPrevious() /DataSeries接口没有定义的方法n n return prev;n n n程序中加黑的语句都是由于添加了getPrevious()方法,而改变了接口DataSeries的方法实现。但是这些方法的接口定义没有任何变化,只是实现接口的类中增加了功能代码,这也是接口的优点之一。 http:/ n 2. 多个类实现(使用)一个接口n【例10.5】我们再用另一个类MyFour来实现接口DataSeries,生成一系列公差为4的数字序列。n class MyFour: DataSeries /用另一种方式实现接口DataSeriesn n int start;n int value;n public MyFour() /构造函数n n start=0;n value=0;n n public int getNext() /实现接口DataSeries中的方法getNext()n n value+=4; http:/ n return value;n n public void reset() /实现接口DataSeries中的方法reset()n n start=0;n value=0;n n public void setStart(int x) /实现接口DataSeries中的方法setStart()n n start=x;n value=x;n n n可以把此处定义的类加到【例10.4】中,则在应用类中的对象可以使用MyFour类的方法来生成公差是4的数列。具体代码构成,用户可自己组织。由此可以看出MyThree和MyFour这些类的对象的接口都是DataSeries接口,也就是说,任何一个类的对象都可以分别实例化接口中定义的功能信息,即接口实现了C#的“一个接口,多种方法”的多态性。 http:/ n 3. 一个类实现(使用)多个接口n使用接口而不使用继承的最大的好处就是,可以在同一个类中同时实现多个接口,即实现多重继承。n要实现多个接口,需将这些接口用逗号分开。 http:/ n【例10.6】在同一个类中实现多个接口。n using System;n public interface IShape /图形接口IShapen n double Area(); /接口方法,计算图形面积n double GramLength(); /接口方法,计算图形边长n int Sides get; /接口属性,获取图形的边长n n public interface IShapePlay /输出计算结果的接口IShapePlayn n void Play();n n public class Square: IShape, IshapePlayn /实现两个接口的类Square,计算正方形面积n n private int sides; /边数n public int SideLength; /边长n public Square() sides=4; /构造函数n public int Sides get return sides; /实现接口IShape中的属性n public double Area()n /实现接口IShape中的方法,计算正方形面积n n return (double) (SideLength* SideLength); http:/ n n public double GramLength () /实现接口IShape中的方法,计算边长n n return (double) (Sides* SideLength);n n public void Play() /实现接口IShapePlay中的方法,输出计算结果n n Console.WriteLine(n计算正方形面积结果如下:);n Console.WriteLine(边长:0, this.SideLength);n Console.WriteLine(边数:0, this.Sides);n Console.WriteLine(面积:0, this.Area();n n n public class MyApp /应用类n n public static void Main()n n Square sq=new Square();n sq.SideLength=8;n sq.Play();n n http:/ n程序运行结果:n计算正方形面积结果如下:n边长:8n边数:4n面积:64n从Square类代码可见,由于该类包含了两个接口,所以它必须实现这两个接口的所有成员。 http:/ 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:/ n例如:n class MyClass: Interface1 Class1 Interface2 Class2n n void MyMethod1() . /方法体代码 n void MyMethod2() /方法体代码 n n以上代码中,MyClass类使用的MyMethod1()方法是在接口Interface1中声明的。当执行上述代码时,先在MyClass类中查找Interface1接口成员的实现,由于在类MyClass中找到了MyMethod1()方法的实现,所以就终止了查找运算。然而,如果在MyClass类没有找到MyMethod1()方法的实现,则还将查找基类Class1和Class2。 http:/ n如果类或结构实现了多个包含相同成员的接口,则这些成员可能会映射到同一个实现上。在以下代码段中,接口IA和IB的Play方法成员都被映射到类C的Play()方法的实现上,由于两个接口的同名方法一定是实现不同的显示目标,则当我们调用Play()方法时将会导致歧义性: n interface IA /接口IAn n void Play (); /方法成员n n interface IB /接口IBn n viod Play(); /方法成员n n class C: IA,I B n n public void Play() n n所以在应用问题中,应根据需要使用显式接口成员实现,来分别为各个接口的成员提供实现。 http:/ n “显式接口成员实现”是指在类中使用接口成员(方法、属性、索引或事件)的完全限定名来定义实现接口成员。接口成员的完全限定名是指依次用点运算符连接命名空间名、最高层基接口名和各层的派生接口名,最后加上成员名构成整个访问名。例如:n namesapce MySpacen n interface IMyinterfacen n void MyMethod();n n interface IYouinterfacen n void YourMethod(); n n n方法MyMethod()的完全限定名为MySpace.IMyinterface.MyMethod()。 http:/ n创建接口成员的显式实现有两个原因:l一个类有可能要同时实现两个接口,如果每个接口都定义了同名、同类型的成员,则在类中采用完全限定名来显式实现各接口中的相应成员,就可以消除歧义。l使用完全限定名来实现一个接口成员时,在类中该接口成员是私有的。n在上述继承接口IA和IB的类C中就必须使用显式接口成员实现来消除歧义性:n class C: IA,I Bn n void IA.Play() /这里不能用public,是私有成员n void IB.Play() /同上 http:/ n【例10.7】将【例10.6】计算正方形面积的代码修改如下:n using System;n public interface IShapen n double Area(); /接口方法,计算图形面积n double GramLength(); /接口方法,计算图形边长n int Sides get; /接口属性,获取图形的边长n void Play(); /接口方法n n public interface IShapePlay n n void Play(); /接口方法 n n public class Square: IShape, IShapePlay /实现两个接口的类Squaren n private int sides; /边数n public int SideLength; /边长n public Square()sides=4; /构造函数n public int Sides get return sides; /实现接口IShape中的属性n public double Area() /实现接口IShape中的方法,计算面积n n return (double) (SideLength* SideLength);n n public double GramLength() /计算边长 n n return (double) (Sides* SideLength);n http:/ n /显示实现IShape接口中的Play()方法,输出文本信息n void IShape.Play() /注意:这个显式实现是个私有成员,不能用public修饰n n Console.WriteLine(n计算面积结果如下:);n Console.WriteLine(边长:0, this.SideLength);n Console.WriteLine(边数:0, this.Sides);n Console.WriteLine(面积:0, this.Area();n n /显示实现接口IShapePlay中的Play()方法,图形显示n void IShapePlay.Play() /注意:这个显式实现是个私有成员,不能用publicn修饰n n Console.WriteLine(显示图形如下:);n n http:/ n public class MyApp /应用类n n public static void Main()n n Square sq=new Square(); /创建一个对象 sqn sq.SideLength=8;n /定义一个IShape接口变量sh。通过强制转换将对象sq看作一个接口IShapen IShape sh=( IShape)sq; n /定义一个IShapePlay接口变量shp,原理同上n IShapePlay shp=( IShapePlay)sq; n sh.Play(); /通过接口变量sh调用显式定义方法IShape.Play()n shp.Play(); /通过接口变量shp调用显式定义方法IShapePlay.Play() n n n程序输出结果:n计算面积结果如下:n边长:8n边数:4n面积:64n图形显示如下: http:/ n本例使用了方法的完全限定名(接口名.方法名),在同一个类Square中显式地实现了两个接口IShape和IShapePlay中的同名方法Play(),在应用类中对定义的显式方法进行了调用。对显式实现的方法的调用不能使用“类名.方法”的格式,因为用类名调用方法不能明确调用哪个方法,所以,要调用不同接口的某个同名方法时,一般有两个常用的方法:l本例中的方法。将类对象强制转换为相应的接口变量,在本例中就是将类对象sq强制转换为接口IShape或IshapePlay的两个相应接口变量sh和shp,可以用这些接口变量来调用相应的接口方法Play()。l在实现方法的类中,分别显式实现两个同名方法,由于显示实现的方法是私有的,所以在类中再相应创建两个用于对这两个显式方法进行调用的调用方法,称其为接口引用调用方法。在应用类中则只要像正常情况下用“对象名.接口引用调用方法名”调用方法即可。这种方法的代码如下,接口代码同【例10.8】,以下只给出实现方法的类和应用类代码: http:/ n public class Square: IShape, IShapePlay /实现两个接口的类Squaren n private int sides; /边数n public int SideLength; /边长n public Square()sides=4; /构造函数n public int Sides get return sides; /实现接口IShape中的属性n public double Area() /实现接口IShape中的方法,计算面积n n return (double) (SideLength* SideLength);n n public double GramLength() /计算边长n n return (double) (Sides* SideLength);n n /显示实现IShape接口中的Play()方法,输出文本信息n void IShape.Play() /注意:这个显式实现是个私有成员,不能用public修饰n n Console.WriteLine(n计算面积结果如下:);n Console.WriteLine(边长:0, this.SideLength);n Console.WriteLine(边数:0, this.Sides);n Console.WriteLine(面积:0, this.Area();n http:/ n /显示实现接口IShapePlay中的Play()方法,图形显示n void IShapePlay.Play() /注意:这个显式实现是个私有成员,不能用public修饰n n Console.WriteLine(显示图形如下:);n n /通过接口引用调用方法Play()n public void Play_A()n n IShape ob_a=this; /当前对象的引用n ob_a.Play(); /调用IShape.Play()方法n n public void Play_B() n n IShapePlay ob_b=this; /当前对象的引用n ob_b.Play(); /调用IShapePlay.Play()方法n n n public class MyApp /应用类n n public static void Main()n n Square sq=new Square(); /创建一个对象 sqn sq.SideLength=8;n sq.Play_A(); /调用IShape.Play()方法 n sq.Play_B(); /调用IShapePlay.Play()方法n n http:/ n 1. 接口之间的继承n在C#中,一个接口可以继承另一个接口,语法和类的继承一样。当一个类要实现从另一个接口继承来的接口,该类就必须提供接口继承链中定义的所有成员的实现。 http:/ n【例10.8】一个接口继承另一个接口。n using System;n public interface IA /接口IAn n void meth1();n void meth2();n n public interface IB:IA /接口IB继承接口IA n void meth3();n n class Myclass: IB /该类必须实现接口IA和IB的所有成员 n n public void meth1()n n Console.WriteLine(实现meth1()方法);n n public void meth2 ()n n Console.WriteLine(实现meth2()方法); http:/ n n public void meth3()n n Console.WriteLine(实现meth3()方法);n n n class Appn n public static void Main()n n Myclass ob=new Myclass ();n ob.meth1(); n ob.meth2();n ob.meth3();n n n程序运行结果:n实现meth1()方法n实现meth2()方法n实现meth3()方法 http:/ n当一个接口继承另一个接口时,如果派生接口的成员和基接口的成员同名,则基接口的成员就会被隐藏,这与类继承时发生的情况一样。如果派生接口的成员不用new修饰符,编译时会发出警告信息。加上new修饰符后即可消除警告。读者可以通过对本例代码的适当修改,来观察隐藏基接口成员的现象。n如果某个接口含有隐藏成员,只有使用new修饰符的成员可被调用。在以下代码中,接口ISon中的方法成员X()隐藏了基接口IFather中的属性成员X: n interface IFathern n int X get;n n interface ISon : IFathern n new int X(); /此处的方法成员X()将隐藏基接口中的同名属性成员X n http:/ n对被隐藏的属性成员X必须提供显式接口成员实现来消除冲突,以下给出了在类中的3种不同的实现方法:l两个接口中的成员都使用显式接口成员实现方式来实现n class C : ISonn n int IFather.X get /使用显式接口成员方式n int ISon.X() /使用显式接口成员方式n l接口ISon中的X成员使用显式接口成员实现方式来实现n class C: ISonn n public int X get n int ISon.X( ) /使用显式接口成员方式 n l接口IFather中的X成员使用显式接口成员实现方式来实现n class C : ISonn n int IFather.X get /使用显式接口成员方式n int X() n http:/ n在多重继承的情况下,如果基接口的一个成员被它的一个派生接口中的同名成员隐藏,则该成员也将在这个派生接口的所有派生接口中都处于隐藏状态。例如:n interface IFather /基接口IFathern n void F(int x);n n interface IBoy: IFather /派生接口IBoyn n new void F(int x); /隐藏基接口IFather中的成员F()n n interface IGirl: IFather /派生接口IGirln n void G();n n interface IMuilt : IBoy, IGirl /也隐藏IBoy的基接口IFather中的成员F()n class Appn n void method(IMuilt y)n n y.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员所隐藏 n n http:/ n 2. 类从基类继承接口映射n类可以从基类继承接口映射,现观察以下代码段:n using System;n interface IAn n void Play();n n class Father: IA /实现接口IAn n public void Play()Console.WriteLine(调用基类方法Play();n n class Son: Father /继承类Father n /隐藏基类方法Play()n new public void Play()Console.WriteLine(调用派生类的新方法Play(); n n class Appn n public static void Main()n n Father f=new Father(); /创建对象实例n Son s=new Son();n IA fa=f; /把对象实例赋给接口实例n IA sa=s;n f.Play(); /调用Father.Play() n s.Play(); /调用Son.Play()n fa.Play(); /调用Father.Play()n sa.Play(); /调用Father.Play()n n http:/ 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:/ n 3. 将接口成员映射到虚方法上n当在接口方法和类的虚方法间创建映射时,其派生类可以重载虚拟方法。所以我们可以将接口方法映射到类中的一个虚方法上,这样,派生类就可通过覆盖基类的虚方法来改变接口的实现,现将上面代码中的类Father的Play()方法定义为虚方法:n using System;n interface IAn n void Play();n n class Father: IA /实现接口IAn n /通过虚方法来实现接口方法 n public virtual void Play()Console.WriteLine(调用基类方法Play(); n n class Son: Father /继承类Fathern n /在派生类中覆盖基类Father的方法Play()n public override void Play()Console.WriteLine(调用派生类的新方法Play(); n n class App http:/ n n public static void Main()n n Father f=new Father(); /创建对象实例n Son s=new Son();n IA fa=f; /把对象实例赋给接口实例n IA sa=s;n f.Play(); /调用Father.Play()n s.Play(); /调用Son.Play()n fa.Play(); /调用Father.Play()n sa.Play(); /调用Son.Play(),与上面代码的调用结果不同n n n程序运行结果:n调用基类方法Play()n调用派生类的新方法Play()n调用基类方法Play()n调用派生类的新方法Play() http:/ n显式接口成员实现时不能被定义为虚成员。但可在显式接口成员实现的过程中调用类的一个虚方法,由于可以在派生类中覆盖这个虚方法,所以同样可以达到调用不同方法的目的。n using System;n interface IAn n void Play();n n class Father:IA /实现接口IAn n void IA.Play() /显示成员实现n n Console.Write(调用虚方法VirtualPlay():); n VirtualPlay();n n public virtual void VirtualPlay()Console.WriteLine(执行Father类中虚方法VirtualPlay(); n n class Son: Father /继承类Fathern n /在派生类中覆盖基类Father的方法Play()n public override void VirtualPlay()Console.WriteLine(执行派生类的新方法VirtualPlay(); n http:/ n class Appn n public static void Main()n n Father f=new Father(); /创建对象实例n Son s=new Son();n IA fa=f; /把对象实例赋给接口实例n IA sa=s;n f.VirtualPlay(); /调用Father.VirtualPlay()n s.VirtualPlay(); /调用Son.VirtualPlay()n fa.Play(); /调用Father类中IA.Play()n sa.Play(); /调用Son.VirtualPlay() n n n程序运行结果:n执行Father类中虚方法VirtualPlay()n执行派生类的新方法VirtualPlay()n调用虚方法VirtualPlay():执行Father类中虚方法VirtualPlay()n调用虚方法VirtualPlay():执行派生类的新方法VirtualPlay() http:/ n 4. 将接口成员映射到抽象方法上n接口成员不仅可以映射到虚方法上,也可以映射到抽象方法上(也叫纯虚方法),所以抽象类也可以实现接口。n interface IAn n void F();n void G();n n abstract class AbsClass: IA /使用抽象类实现接口n n public abstract void F();n public abstract void G(); n http:/ n接口的重新实现是指某个类可以再次实现已被它的基类实现的接口,即继承借口实现的类可以重新实现该接口。其方法是把接口的名称置于基类列表中,当重新实现接口后,派生类从基类继承的接口映射不会影响为接口的重实现建立的接口映射。 http:/ n【例10.9】在以下代码中,类CFather实现接口IFather,类CSon从类CFather派生,并重新实现了IFather接口的部分成员(Play()方法和Clear()方法)。n using System;n namespace Ifacen n public interface IFather /接口IFathern n int x get; set; /属性n int y get; set; n void Play(); /方法n void Clear();n n class CFather : IFather /类CFather n n public int x /实现接口IFather中的属性xn n getn n Console.WriteLine(在CFather类中实现属性x);n return 0;n n setn n Console.WriteLine(在CFather类中实现属性x);n n n public int y /实现接口IFather中的属性yn n getn n Console.WriteLine(在CFather类中实现属性y);n return 0;n n set http:/ n n Console.WriteLine(在CFather类中实现属性y);n n n public void Play()n n Console.WriteLine(在CFather类中实现Play()方法);n n public void Clear()n n Console.WriteLine(在CFather类中实现Clear()方法);n n n class CSon: CFather , IFathern n private int mx;n private int my;n private string mtext;n new public void Play() /重新实现接口IFather中定义的Play()方法n n Console.WriteLine(在CSon类中实现Play()方法);n n public string Textn n getreturn mtext;n setmtext=value;n n new public void Clear() /重新实现接口IFather中定义的Clear()方法n n this.Text=;n Console.WriteLine(在CSon类中实现Clear()方法);n n http:/ n class Appn n public static void Main(string args)n n CSon ctext=new CSon();n Console.WriteLine(通过类CSon的实例调用接口成员);n ctext.x=0;/由于类CSon中没有实现属性x,所以调用其基类CFather中的n x属性实现n ctext.y=0; /原因同上。n ctext.Play(); /类CSon中的Play方法覆盖其基类CFather中的Play方法n ctext.Clear(); /原因同上。n Console.WriteLine(); n Console.WriteLine(通过接口IFather的实例调用接口成员);n IFather itext=ctext;n itext.x=1; /接口IFather的属性x被映射到类CFather中实现n itext.y=1; / 原因同上n itext.Play(); /接口IFather中的Play方法被映射到派生类CSon中Play方n法的实现上n itext.Clear(); /原因同上n Console.WriteLine();n Console.WriteLine(通过类CFather的实例调用接口成员);n CFather obtext=ctext;n /通过类CFather的对象调用的接口成员全部是类CFather提供的实现,n /说明CFather类的接口映射没有被CSon类的重新实现所改变 n obtext.x=1; n obtext.y=1; n obtext.Play(); n obtext.Clear(); n n n http:/ n程序运行结果:n通过类CSon的实例调用接口成员n在CFather类中实现属性xn在CFather类中实现属性yn在CSon类中实现Play()方法n在CSon类中实现Clear()方法n通过接口IFather的实例调用接口成员n在CFath
展开阅读全文