詳細介紹C#中的繼承機制
繼承是指一個對象直接使用另一對象的屬性和方法。在編程中也沿用了繼承的概念,在面向?qū)ο缶幊讨?,都有類的繼承。下面介紹C#中的繼承機制。
一. 繼承基礎知識
為了提高軟件模塊的可復用性和可擴充性,以便提高軟件的開發(fā)效率,我們總是希望能夠利用前人或自己以前的開發(fā)成果,同時又希望在自己的開發(fā)過程中能夠有足夠的靈活性,不拘泥于復用的模塊。C#這種完全面向?qū)ο蟮某绦蛟O計語言提供了兩個重要的特性--繼承性inheritance和多態(tài)性polymorphism。
繼承是面向?qū)ο蟪绦蛟O計的主要特征之一,它可以讓您重用代碼,可以節(jié)省程序設計的時間。繼承就是在類之間建立一種相交關系,使得新定義的派生類的實例可以繼承已有的基類的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起類的新層次。
現(xiàn)實世界中的許多實體之間不是相互孤立的,它們往往具有共同的特征也存在內(nèi)在的差別。人們可以采用層次結構來描述這些實體之間的相似之處和不同之處。
圖1 類圖
上圖反映了交通工具類的派生關系。***層的實體往往具有最一般最普遍的特征,越下層的事物越具體,并且下層包含了上層的特征。它們之間的關系是基類與派生類之間的關系。
為了用軟件語言對現(xiàn)實世界中的層次結構進行模型化,面向?qū)ο蟮某绦蛟O計技術引入了繼承的概念。一個類從另一個類派生出來時,派生類從基類那里繼承特性。派生類也可以作為其它類的基類。從一個基類派生出來的多層類形成了類的層次結構。
注意:C#中,派生類只能從一個類中繼承。這是因為,在C++中,人們在大多數(shù)情況下不需要一個從多個類中派生的類。從多個基類中派生一個類這往往會帶來許多問題,從而抵消了這種靈活性帶來的優(yōu)勢。
C#中,派生類從它的直接基類中繼承成員:方法、域、屬性、事件、索引指示器。除了構造函數(shù)和析構函數(shù),派生類隱式地繼承了直接基類的所有成員??聪旅媸纠?/p>
- using System ;
- class Vehicle //定義交通工具(汽車)類
- {
- protected int wheels ; //公有成員:輪子個數(shù)
- protected float weight ; //保護成員:重量
- public Vehicle( ){;}
- public Vehicle(int w,float g){
- wheels = w ;
- weight = g ;
- }
- public void Speak( ){
- Console.WriteLine( "交通工具的輪子個數(shù)是可以變化的! " ) ;
- }
- } ;
- class Car:Vehicle //定義轎車類:從汽車類中繼承
- {
- int passengers ; //私有成員:乘客數(shù)
- public Car(int w , float g , int p) : base(w, g)
- {
- wheels = w ;
- weight = g ;
- passengers=p ;
- }
- }
Vehicle 作為基類,體現(xiàn)了"汽車"這個實體具有的公共性質(zhì):汽車都有輪子和重量。Car 類繼承了Vehicle的這些性質(zhì),并且添加了自身的特性:可以搭載乘客。
#p#
二、C#中的繼承符合下列規(guī)則:
1、繼承是可傳遞的。如果C從B中派生,B又從A中派生,那么C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object類作為所有類的基類。
2、派生類應當是對基類的擴展。派生類可以添加新的成員,但不能除去已經(jīng)繼承的成員的定義。
3、構造函數(shù)和析構函數(shù)不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承。基類中成員的訪問方式只能決定派生類能否訪問它們。
4、派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋已繼承的成員。但這并不因為這派生類刪除了這些成員,只是不能再訪問這些成員。
5、類可以定義虛方法、虛屬性以及虛索引指示器,它的派生類能夠重載這些成員,從而實現(xiàn)類可以展示出多態(tài)性。
6、派生類只能從一個類中繼承,可以通過接呂實現(xiàn)多重繼承。
下面的代碼是一個子類繼承父類的例子:
- using System ;
- public class ParentClass
- {
- public ParentClass( )
- { Console.WriteLine("父類構造函數(shù)。"); }
- public void print( )
- { Console.WriteLine("I'm a Parent Class。") ; }
- }
- public class ChildClass : ParentClass
- {
- public ChildClass( )
- { Console.WriteLine("子類構造函數(shù)。") ; }
- public static void Main( ) {
- ChildClass child = new ChildClass( ) ;
- child.print( ) ;
- }
- }
程序運行輸出:
父類構造函數(shù)。子類構造函數(shù)。I'm a Parent Class。
上面的一個類名為ParentClass,main函數(shù)中用到的類名為ChildClass。要做的是創(chuàng)建一個使用父類ParentClass現(xiàn)有代碼的子類ChildClass。
1.首先必須說明ParentClass是ChildClass的基類。
這是通過在ChildClass類中作出如下說明來完成的:"public class ChildClass :ParentClass"。在派生類標識符后面,用分號":"來表明后面的標識符是基類。C#僅支持單一繼承。因此,你只能指定一個基類。
2.ChildClass的功能幾乎等同于ParentClass。
因此,也可以說ChildClass "就是" ParentClass。在ChildClass 的Main()方法中,調(diào)用print( ) 方法的結果,就驗證這一點。該子類并沒有自己的print( )方法,它使用了ParentClass中的print( )方法。在輸出結果中的第三行可以得到驗證。
3.基類在派生類初始化之前自動進行初始化。ParentClass類的構造函數(shù)在ChildClass的構造函數(shù)之前執(zhí)行。
#p#
三. 訪問與隱藏基類成員
(1) 訪問基類成員
通過base 關鍵字訪問基類的成員:
- 調(diào)用基類上已被其他方法重寫的方法。
- 指定創(chuàng)建派生類實例時應調(diào)用的基類構造函數(shù)。
- 基類訪問只能在構造函數(shù)、實例方法或?qū)嵗龑傩栽L問器中進行。
- 從靜態(tài)方法中使用 base 關鍵字是錯誤的。
示例:下面程序中基類 Person 和派生類 Employee 都有一個名為 Getinfo 的方法。通過使用 base關鍵字,可以從派生類中調(diào)用基類上的 Getinfo 方法。
- using System ;
- public class Person
- {
- protected string ssn = "111-222-333-444" ;
- protected string name = "張三" ;
- public virtual void GetInfo() {
- Console.WriteLine("姓名: {0}", name) ;
- Console.WriteLine("編號: {0}", ssn) ;
- }
- }
- class Employee: Person
- {
- public string id = "ABC567EFG23267" ;
- public override void GetInfo() {
- // 調(diào)用基類的GetInfo方法:
- base.GetInfo();
- Console.WriteLine("成員ID: {0}", id) ;
- }
- }
- class TestClass {
- public static void Main() {
- Employee E = new Employee() ;
- E.GetInfo() ;
- }
- }
程序運行輸出:
- 姓名: 張三
- 編號: 111-222-333-444
- 成員ID: ABC567EFG23267
示例:派生類同基類進行通信。
- using System ;
- public class Parent
- {
- string parentString;
- public Parent( )
- { Console.WriteLine("Parent Constructor.") ; }
- public Parent(string myString) {
- parentString = myString;
- Console.WriteLine(parentString) ;
- }
- public void print( )
- { Console.WriteLine("I'm a Parent Class.") ; }
- }
- public class Child : Parent
- {
- public Child( ) : base("From Derived")
- { Console.WriteLine("Child Constructor.") ; }
- public void print( ) {
- base.print( ) ;
- Console.WriteLine("I'm a Child Class.") ;
- }
- public static void Main( ) {
- Child child = new Child( ) ;
- child.print( ) ;
- ((Parent)child).print( ) ;
- }
- }
程序運行輸出:
- From Derived
- Child Constructor.
- I'm a Parent Class.
- I'm a Child Class.
- I'm a Parent Class.
說明:
1.派生類在初始化的過程中可以同基類進行通信。
上面代碼演示了在子類的構造函數(shù)定義中是如何實現(xiàn)同基類通信的。分號":"和關鍵字base用來調(diào)用帶有相應參數(shù)的基類的構造函數(shù)。輸出結果中,***行表明:基類的構造函數(shù)***被調(diào)用,其實在參數(shù)是字符串"From Derived"。
2.有時,對于基類已有定義的方法,打算重新定義自己的實現(xiàn)。
Child類可以自己重新定義print( )方法的實現(xiàn)。Child的print( )方法覆蓋了Parent中的 print 方法。結果是:除非經(jīng)過特別指明,Parent類中的print方法不會被調(diào)用。
3.在Child類的 print( ) 方法中,我們特別指明:調(diào)用的是Parent類中的 print( ) 方法。
方法名前面為"base",一旦使用"base"關鍵字之后,你就可以訪問基類的具有公有或者保護權限的成員。 Child類中的print( )方法的執(zhí)行結果出現(xiàn)上面的第三行和第四行。
4.訪問基類成員的另外一種方法是:通過顯式類型轉(zhuǎn)換。
在Child類的Main( )方法中的***一條語句就是這么做的。記?。号缮愂瞧浠惖奶乩?。這個事實告訴我們:可以在派生類中進行數(shù)據(jù)類型的轉(zhuǎn)換,使其成為基類的一個實例。上面代碼的***一行實際上執(zhí)行了Parent類中的 print( )方法。
(2)隱藏基類成員
想想看,如果所有的類都可以被繼承,繼承的濫用會帶來什么后果?類的層次結構體系將變得十分龐,大類之間的關系雜亂無章,對類的理解和使用都會變得十分困難。有時候,我們并不希望自己編寫的類被繼承。另一些時候,有的類已經(jīng)沒有再被繼承的必要。C#提出了一個密封類(sealed class)的概念,幫助開發(fā)人員來解決這一問題。
密封類在聲明中使用sealed 修飾符,這樣就可以防止該類被其它類繼承。如果試圖將一個密封類作為其它類的基類,C#將提示出錯。理所當然,密封類不能同時又是抽象類,因為抽象總是希望被繼承的。
在哪些場合下使用密封類呢?密封類可以阻止其它程序員在無意中繼承該類。而且密封類可以起到運行時優(yōu)化的效果。實際上,密封類中不可能有派生類。如果密封類實例中存在虛成員函數(shù),該成員函數(shù)可以轉(zhuǎn)化為非虛的,函數(shù)修飾符virtual 不再生效。
讓我們看下面的例子:
- bstract class A
- {
- public abstract void F( ) ;
- }
- sealed class B: A
- {
- public override void F( )
- { // F 的具體實現(xiàn)代碼 }
- }
如果我們嘗試寫下面的代碼
- class C: B{ }
C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。
(3) 密封方法
我們已經(jīng)知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod)的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。
不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實現(xiàn)方法。所以,在方法的聲明中,sealed 修飾符總是和override 修飾符同時使用。請看下面的例子代碼:
- using System ;
- class A
- {
- public virtual void F( )
- { Console.WriteLine("A.F") ; }
- public virtual void G( )
- { Console.WriteLine("A.G") ; }
- }
- class B: A
- {
- sealed override public void F( )
- { Console.WriteLine("B.F") ; }
- override public void G( )
- { Console.WriteLine("B.G") ; }
- }
- class C: B
- {
- override public void G( )
- { Console.WriteLine("C.G") ; }
- }
類B 對基類A 中的兩個虛方法均進行了重載,其中F 方法使用了sealed 修飾符,成為一個密封方法。G 方法不是密封方法,所以在B 的派生類C 中,可以重載方法G,但不能重載方法F。
(4) 使用 new 修飾符隱藏基類成員
使用 new 修飾符可以顯式隱藏從基類繼承的成員。若要隱藏繼承的成員,請使用相同名稱在派生類中聲明該成員,并用 new 修飾符修飾它。
請看下面的類:
- public class MyBase
- {
- public int x ;
- public void MyVoke() ;
- }
在派生類中用 MyVoke名稱聲明成員會隱藏基類中的 MyVoke方法,即:
- public class MyDerived : MyBase
- { new public void MyVoke (); }
但是,因為字段 x 不是通過類似名隱藏的,所以不會影響該字段。
通過繼承隱藏名稱采用下列形式之一:
a、引入類或結構中的常數(shù)、指定、屬性或類型隱藏具有相同名稱的所有基類成員。
b、引入類或結構中的方法隱藏基類中具有相同名稱的屬性、字段和類型。同時也隱藏具有相同簽名的所有基類方法。
c、引入類或結構中的索引器將隱藏具有相同名稱的所有基類索引器。
注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隱藏繼承成員的聲明中使用 new 修飾符將發(fā)出警告。
示例1:在該例中,基類 MyBaseC 和派生類 MyDerivedC 使用相同的字段名 x,從而隱藏了繼承字段的值。該例說明了 new 修飾符的使用。同時也說明了如何使用完全限定名訪問基類的隱藏成員。
- using System ;
- public class MyBase
- {
- public static int x = 55 ;
- public static int y = 22 ;
- }
- public class MyDerived : MyBase
- {
- new public static int x = 100; // 利用new 隱藏基類的x
- public static void Main()
- {
- // 打印x:
- Console.WriteLine(x);
- //訪問隱藏基類的 x:
- Console.WriteLine(MyBase.x);
- //打印不隱藏的y:
- Console.WriteLine(y);
- }
- }
輸出: 100 55 22
如果移除 new 修飾符,程序?qū)⒗^續(xù)編譯和運行,但您會收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套類型正在隱藏另一種類型,如下例所示,也可以使用 new 修飾符修改此嵌套類型。
#p#
四、多級繼承
一些面向?qū)ο笳Z言允許一個類從多個基類中繼承,而另一些面向?qū)ο笳Z言只允許從一個類繼承,但可以隨意從幾個接口或純抽象類中繼承。
只有C++支持多級繼承,許多程序員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,所以C#中類的繼承只可以是一個,即子類只能派生于一個父類,而有時你必須繼承多個類的特性,為了實現(xiàn)多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:
- using System ;
- //定義一個描述點的接口
- interface IPoint
- {
- int x {
- get ;
- set ;
- }
- int y {
- get ;
- set ;
- }
- }
- interface IPoint2
- {
- int y {
- get ;
- set ;
- }
- }
- //在point中繼承了兩個父類接口,并分別使用了兩個父類接口的方法
- class Point:IPoint,IPoint2
- {
- //定義兩個類內(nèi)部訪問的私有成員變量
- private int pX ;
- private int pY ;
- public Point(int x,int y) {
- pX=x ;
- pY=y ;
- }
- //定義的屬性,IPoint接口方法實現(xiàn)
- public int x
- {
- get
- { return pX ; }
- set
- { pX =value ; }
- }
- //IPoint1接口方法實現(xiàn)
- public int y
- {
- get
- { return pY ; }
- set
- { pY =value ; }
- }
- }
- class Test
- {
- private static void OutPut( IPoint p )
- { Console.WriteLine("x={0},y={1}",p.x,p.y) ; }
- public static void Main( ) {
- Point p =new Point(15,30) ;
- Console.Write("The New Point is:") ;
- OutPut( p ) ;
- string myName =Console.ReadLine( ) ;
- Console.Write("my name is {0}", myName) ;
- }
希望通過以上四個方面的介紹,能夠讓你對于C#中的繼承機制有了更深一步的了解。


2009-08-03 18:49:17
2009-08-13 16:02:29
2009-08-13 15:40:28
2011-06-08 13:35:18
2010-01-19 18:51:17
2009-08-06 14:59:36




