详细介绍C#中的继承机制

开发 后端
我们都知道,继承机制是三大机制之一,重要性不用说大家也知道。本文介绍的是C#中的继承机制。希望通过本文的介绍,能给大家带来帮助。

继承是指一个对象直接使用另一对象的属性和方法。在编程中也沿用了继承的概念,在面向对象编程中,都有类的继承。下面介绍C#中的继承机制。

一. 继承基础知识

为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在自己的开发过程中能够有足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性--继承性inheritance和多态性polymorphism。

继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。

现实世界中的许多实体之间不是相互孤立的,它们往往具有共同的特征也存在内在的差别。人们可以采用层次结构来描述这些实体之间的相似之处和不同之处。

继承机制图一

图1 类图

上图反映了交通工具类的派生关系。***层的实体往往具有最一般最普遍的特征,越下层的事物越具体,并且下层包含了上层的特征。它们之间的关系是基类与派生类之间的关系。

为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。
注意:C#中,派生类只能从一个类中继承。这是因为,在C++中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中派生一个类这往往会带来许多问题,从而抵消了这种灵活性带来的优势。

C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。看下面示例:

  1. using System ;  
  2. class Vehicle //定义交通工具(汽车)类  
  3. {  
  4. protected int wheels ; //公有成员:轮子个数  
  5. protected float weight ; //保护成员:重量  
  6. public Vehicle( ){;}  
  7. public Vehicle(int w,float g){  
  8. wheels = w ;  
  9. weight = g ;  
  10. }  
  11. public void Speak( ){  
  12. Console.WriteLine( "交通工具的轮子个数是可以变化的! " ) ;  
  13. }  
  14. } ;  
  15. class Car:Vehicle //定义轿车类:从汽车类中继承  
  16. {  
  17. int passengers ; //私有成员:乘客数  
  18. public Car(int w , float g , int p) : base(w, g)  
  19. {  
  20. wheels = w ;  
  21. weight = g ;  
  22. passengers=p ;  
  23. }  
  24. }  

Vehicle 作为基类,体现了"汽车"这个实体具有的公共性质:汽车都有轮子和重量。Car 类继承了Vehicle的这些性质,并且添加了自身的特性:可以搭载乘客。

#p#

二、C#中的继承符合下列规则:

1、继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object类作为所有类的基类。

2、派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。

3、构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。

4、派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。

5、类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。

6、派生类只能从一个类中继承,可以通过接吕实现多重继承。

下面的代码是一个子类继承父类的例子:

  1. using System ;  
  2. public class ParentClass  
  3. {  
  4. public ParentClass( )  
  5. { Console.WriteLine("父类构造函数。"); }  
  6. public void print( )  
  7. { Console.WriteLine("I'm a Parent Class。") ; }  
  8. }  
  9. public class ChildClass : ParentClass  
  10. {  
  11. public ChildClass( )  
  12. { Console.WriteLine("子类构造函数。") ; }  
  13. public static void Main( ) {  
  14. ChildClass child = new ChildClass( ) ;  
  15. child.print( ) ;  
  16. }  
  17. }  

程序运行输出:

父类构造函数。子类构造函数。I'm a Parent Class。

上面的一个类名为ParentClass,main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类ChildClass。

1.首先必须说明ParentClass是ChildClass的基类。

这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass :ParentClass"。在派生类标识符后面,用分号":"来表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。

2.ChildClass的功能几乎等同于ParentClass。

因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main()方法中,调用print( ) 方法的结果,就验证这一点。该子类并没有自己的print( )方法,它使用了ParentClass中的print( )方法。在输出结果中的第三行可以得到验证。

3.基类在派生类初始化之前自动进行初始化。ParentClass类的构造函数在ChildClass的构造函数之前执行。

#p#

三. 访问与隐藏基类成员

(1) 访问基类成员

通过base 关键字访问基类的成员:

  • 调用基类上已被其他方法重写的方法。
  • 指定创建派生类实例时应调用的基类构造函数。
  • 基类访问只能在构造函数、实例方法或实例属性访问器中进行。
  • 从静态方法中使用 base 关键字是错误的。

示例:下面程序中基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base关键字,可以从派生类中调用基类上的 Getinfo 方法。

  1. using System ;  
  2. public class Person  
  3. {  
  4. protected string ssn = "111-222-333-444" ;  
  5. protected string name = "张三" ;  
  6. public virtual void GetInfo() {  
  7. Console.WriteLine("姓名: {0}", name) ;  
  8. Console.WriteLine("编号: {0}", ssn) ;  
  9. }  
  10. }  
  11. class Employee: Person  
  12. {  
  13. public string id = "ABC567EFG23267" ;  
  14. public override void GetInfo() {  
  15. // 调用基类的GetInfo方法:  
  16. base.GetInfo();  
  17. Console.WriteLine("成员ID: {0}", id) ;  
  18. }  
  19. }  
  20. class TestClass {  
  21. public static void Main() {  
  22. Employee E = new Employee() ;  
  23. E.GetInfo() ;  
  24. }  
  25. }  

程序运行输出:

  1.  姓名: 张三  
  2.  编号: 111-222-333-444  
  3.  成员ID: ABC567EFG23267   

示例:派生类同基类进行通信。

  1. using System ;  
  2. public class Parent  
  3. {  
  4. string parentString;  
  5. public Parent( )  
  6. { Console.WriteLine("Parent Constructor.") ; }  
  7. public Parent(string myString) {  
  8. parentString = myString;  
  9. Console.WriteLine(parentString) ;  
  10. }  
  11. public void print( )  
  12. { Console.WriteLine("I'm a Parent Class.") ; }  
  13. }  
  14. public class Child : Parent  
  15. {  
  16. public Child( ) : base("From Derived")  
  17. { Console.WriteLine("Child Constructor.") ; }  
  18. public void print( ) {  
  19. base.print( ) ;  
  20. Console.WriteLine("I'm a Child Class.") ;  
  21. }  
  22. public static void Main( ) {  
  23. Child child = new Child( ) ;  
  24. child.print( ) ;  
  25. ((Parent)child).print( ) ;  
  26. }  
  27. }  

程序运行输出:

  1. From Derived  
  2. Child Constructor.  
  3. I'm a Parent Class.  
  4. I'm a Child Class.  
  5. I'm a Parent Class. 

说明:

1.派生类在初始化的过程中可以同基类进行通信。

上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,***行表明:基类的构造函数***被调用,其实在参数是字符串"From Derived"。

2.有时,对于基类已有定义的方法,打算重新定义自己的实现。

Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。

3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。

方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。

4.访问基类成员的另外一种方法是:通过显式类型转换。

在Child类的Main( )方法中的***一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的***一行实际上执行了Parent类中的 print( )方法。

(2)隐藏基类成员

想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed class)的概念,帮助开发人员来解决这一问题。

密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。

在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。

让我们看下面的例子:

  1. bstract class A  
  2. {  
  3. public abstract void F( ) ;  
  4. }  
  5. sealed class B: A  
  6. {  
  7. public override void F( )  
  8. // F 的具体实现代码 }  

如果我们尝试写下面的代码

  1. class C: B{ } 

C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。

(3) 密封方法

我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod)的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。

不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:

  1. using System ;  
  2. class A  
  3. {  
  4. public virtual void F( )  
  5. { Console.WriteLine("A.F") ; }  
  6. public virtual void G( )  
  7. { Console.WriteLine("A.G") ; }  
  8. }  
  9. class B: A  
  10. {  
  11. sealed override public void F( )  
  12. { Console.WriteLine("B.F") ; }  
  13. override public void G( )  
  14. { Console.WriteLine("B.G") ; }  
  15. }  
  16. class C: B  
  17. {  
  18. override public void G( )  
  19. { Console.WriteLine("C.G") ; }  

类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。

(4) 使用 new 修饰符隐藏基类成员

使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。

请看下面的类:

  1. public class MyBase  
  2. {  
  3. public int x ;  
  4. public void MyVoke() ;  

在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:

  1. public class MyDerived : MyBase  
  2. new public void MyVoke (); } 

但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。

通过继承隐藏名称采用下列形式之一:

a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。

b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。

c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。

注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。

示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。

  1. using System ;  
  2. public class MyBase  
  3. {  
  4. public static int x = 55 ;  
  5. public static int y = 22 ;  
  6. }  
  7. public class MyDerived : MyBase  
  8. {  
  9. new public static int x = 100; // 利用new 隐藏基类的x  
  10. public static void Main()  
  11. {  
  12. // 打印x:  
  13. Console.WriteLine(x);  
  14. //访问隐藏基类的 x:  
  15. Console.WriteLine(MyBase.x);  
  16. //打印不隐藏的y:  
  17. Console.WriteLine(y);  
  18. }  

输出: 100 55 22

如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:

The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.

如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。

#p#

四、多级继承

一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。

只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:

  1. using System ;  
  2. //定义一个描述点的接口  
  3. interface IPoint  
  4. {  
  5. int x {  
  6. get ;  
  7. set ;  
  8. }  
  9. int y {  
  10. get ;  
  11. set ;  
  12. }  
  13. }  
  14. interface IPoint2  
  15. {  
  16. int y {  
  17. get ;  
  18. set ;  
  19. }  
  20. }  
  21. //在point中继承了两个父类接口,并分别使用了两个父类接口的方法  
  22. class Point:IPoint,IPoint2  
  23. {  
  24. //定义两个类内部访问的私有成员变量  
  25. private int pX ;  
  26. private int pY ;  
  27. public Point(int x,int y) {  
  28. pX=x ;  
  29. pY=y ;  
  30. }  
  31. //定义的属性,IPoint接口方法实现  
  32. public int x  
  33. {  
  34. get 
  35. return pX ; }  
  36. set 
  37. { pX =value ; }  
  38. }  
  39. //IPoint1接口方法实现  
  40. public int y  
  41. {  
  42. get 
  43. return pY ; }  
  44. set 
  45. { pY =value ; }  
  46. }  
  47. }  
  48. class Test  
  49. {  
  50. private static void OutPut( IPoint p )  
  51. { Console.WriteLine("x={0},y={1}",p.x,p.y) ; }  
  52. public static void Main( ) {  
  53. Point p =new Point(15,30) ;  
  54. Console.Write("The New Point is:") ;  
  55. OutPut( p ) ;  
  56. string myName =Console.ReadLine( ) ;  
  57. Console.Write("my name is {0}", myName) ;  

希望通过以上四个方面的介绍,能够让你对于C#中的继承机制有了更深一步的了解。

责任编辑:于铁 来源: 互联网
相关推荐

2009-08-12 15:34:40

C# DBNull

2009-08-10 16:30:56

C# BitmapDa

2009-09-03 11:00:29

C#反射机制

2009-08-13 13:38:30

C#命名规范

2009-08-14 17:04:50

C#类型系统

2009-08-07 16:10:20

C#调用API

2009-08-24 18:21:23

C# ListView

2009-08-26 17:31:59

C# const常量

2009-08-20 15:26:42

C#循环语句

2009-08-21 15:16:23

C#使用指针

2009-08-21 09:23:11

C# GDI+

2009-08-03 18:49:17

C#和Java

2009-08-13 16:02:29

C#结构

2010-01-19 18:51:17

C++类

2009-08-13 15:40:28

C#基础知识

2009-08-27 17:31:44

C#创建Windows

2011-06-08 13:35:18

C#数据类型

2009-08-06 14:59:36

C#编译器

2009-08-27 14:32:15

C#编写ActiveX

2009-08-25 17:28:23

C#创建DataSet
点赞
收藏

51CTO技术栈公众号