天天看點

C#的繼承機制

一. 繼承基礎知識

  為了提高軟體子產品的可複用性和可擴充性,以便提高軟體的開發效率,我們總是希望能夠利用前人或自己以前的開發成果,同時又希望在自己的開發過程中能夠有足夠的靈活性,不拘泥于複用的子產品。C#這種完全面向對象的程式設計語言提供了兩個重要的特性--繼承性inheritance 和多态性polymorphism。

  繼承是面向對象程式設計的主要特征之一,它可以讓您重用代碼,可以節省程式設計的時間。繼承就是在類之間建立一種相交關系,使得新定義的派生類的執行個體可以繼承已有的基類的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起類的新層次。

  現實世界中的許多實體之間不是互相孤立的,它們往往具有共同的特征也存在内在的差别。人們可以采用層次結構來描述這些實體之間的相似之處和不同之處。

  上圖反映了交通工具類的派生關系。最高層的實體往往具有最一般最普遍的特征,越下層的事物越具體,并且下層包含了上層的特征。它們之間的關系是基類與派生類之間的關系。

  為了用軟體語言對現實世界中的層次結構進行模型化,面向對象的程式設計技術引入了繼承的概念。一個類從另一個類派生出來時,派生類從基類那裡繼承特性。派生類也可以作為其它類的基類。從一個基類派生出來的多層類形成了類的層次結構。

  注意:C#中,派生類隻能從一個類中繼承。這是因為,在C++中,人們在大多數情況下不需要一個從多個類中派生的類。從多個基類中派生一個類這往往會帶來許多問題,進而抵消了這種靈活性帶來的優勢。

C#中,派生類從它的直接基類中繼承成員:方法、域、屬性、事件、索引訓示器。除了構造函數和析構函數,派生類隐式地繼承了直接基類的所有成員。看下面示例:

using System ;

class Vehicle //定義交通工具(汽車)類

{

protected int wheels ; //公有成員:輪子個數

protected float weight ; //保護成員:重量

public Vehicle( ){;}

public Vehicle(int w,float g){

wheels = w ;

weight = g ;

}

public void Speak( ){

Console.WriteLine( "交通工具的輪子個數是可以變化的! " ) ;

}

} ;

class Car:Vehicle //定義轎車類:從汽車類中繼承

{

int passengers ; //私有成員:乘客數

public Car(int w , float g , int p) : base(w, g)

{

wheels = w ;

weight = g ;

passengers=p ;

}

}

  Vehicle 作為基類,展現了"汽車"這個實體具有的公共性質:汽車都有輪子和重量。Car 類繼承了Vehicle 的這些性質,并且添加了自身的特性:可以搭載乘客。

二、C#中的繼承符合下列規則:

  1、繼承是可傳遞的。如果C從B中派生,B又從A中派生,那麼C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object 類作為所有類的基類。

  2、派生類應當是對基類的擴充。派生類可以添加新的成員,但不能除去已經繼承的成員的定義。

  3、構造函數和析構函數不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的通路方式,都能被繼承。基類中成員的通路方式隻能決定派生類能否通路它們。

  4、派生類如果定義了與繼承而來的成員同名的新成員,就可以覆寫已繼承的成員。但這并不因為這派生類删除了這些成員,隻是不能再通路這些成員。

  5、類可以定義虛方法、虛屬性以及虛索引訓示器,它的派生類能夠重載這些成員,進而實作類可以展示出多态性。

  6、派生類隻能從一個類中繼承,可以通過接呂實作多重繼承。

  下面的代碼是一個子類繼承父類的例子:

using System ;

public class ParentClass

{

public ParentClass( )

{ Console.WriteLine("父類構造函數。"); }

public void print( )

{ Console.WriteLine("I'm a Parent Class。") ; }

}

public class ChildClass : ParentClass

{

public ChildClass( )

{ Console.WriteLine("子類構造函數。") ; }

public static void Main( ) {

ChildClass child = new ChildClass( ) ;

child.print( ) ;

}

}

  程式運作輸出:

  父類構造函數。子類構造函數。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的構造函數之前執行。

三. 通路與隐藏基類成員

  (1) 通路基類成員

  通過base 關鍵字通路基類的成員:

   調用基類上已被其他方法重寫的方法。

   指定建立派生類執行個體時應調用的基類構造函數。

   基類通路隻能在構造函數、執行個體方法或執行個體屬性通路器中進行。

   從靜态方法中使用 base 關鍵字是錯誤的。

  示例:下面程式中基類 Person 和派生類 Employee 都有一個名為 Getinfo 的方法。通過使用 base 關鍵字,可以從派生類中調用基類上的 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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

public class ParentClass

{

public ParentClass( )

{ Console.WriteLine("父類構造函數。"); }

public void print( )

{ Console.WriteLine("I'm a Parent Class。") ; }

}

public class ChildClass : ParentClass

{

public ChildClass( )

{ Console.WriteLine("子類構造函數。") ; }

public static void Main( ) {

ChildClass child = new ChildClass( ) ;

child.print( ) ;

}

}

  程式運作輸出:

  父類構造函數。子類構造函數。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的構造函數之前執行。

三. 通路與隐藏基類成員

  (1) 通路基類成員

  通過base 關鍵字通路基類的成員:

   調用基類上已被其他方法重寫的方法。

   指定建立派生類執行個體時應調用的基類構造函數。

   基類通路隻能在構造函數、執行個體方法或執行個體屬性通路器中進行。

   從靜态方法中使用 base 關鍵字是錯誤的。

  示例:下面程式中基類 Person 和派生類 Employee 都有一個名為 Getinfo 的方法。通過使用 base 關鍵字,可以從派生類中調用基類上的 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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

class Vehicle //定義交通工具(汽車)類

{

protected int wheels ; //公有成員:輪子個數

protected float weight ; //保護成員:重量

public Vehicle( ){;}

public Vehicle(int w,float g){

wheels = w ;

weight = g ;

}

public void Speak( ){

Console.WriteLine( "交通工具的輪子個數是可以變化的! " ) ;

}

} ;

class Car:Vehicle //定義轎車類:從汽車類中繼承

{

int passengers ; //私有成員:乘客數

public Car(int w , float g , int p) : base(w, g)

{

wheels = w ;

weight = g ;

passengers=p ;

}

}

  Vehicle 作為基類,展現了"汽車"這個實體具有的公共性質:汽車都有輪子和重量。Car 類繼承了Vehicle 的這些性質,并且添加了自身的特性:可以搭載乘客。

二、C#中的繼承符合下列規則:

  1、繼承是可傳遞的。如果C從B中派生,B又從A中派生,那麼C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object 類作為所有類的基類。

  2、派生類應當是對基類的擴充。派生類可以添加新的成員,但不能除去已經繼承的成員的定義。

  3、構造函數和析構函數不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的通路方式,都能被繼承。基類中成員的通路方式隻能決定派生類能否通路它們。

  4、派生類如果定義了與繼承而來的成員同名的新成員,就可以覆寫已繼承的成員。但這并不因為這派生類删除了這些成員,隻是不能再通路這些成員。

  5、類可以定義虛方法、虛屬性以及虛索引訓示器,它的派生類能夠重載這些成員,進而實作類可以展示出多态性。

  6、派生類隻能從一個類中繼承,可以通過接呂實作多重繼承。

  下面的代碼是一個子類繼承父類的例子:

using System ;

public class ParentClass

{

public ParentClass( )

{ Console.WriteLine("父類構造函數。"); }

public void print( )

{ Console.WriteLine("I'm a Parent Class。") ; }

}

public class ChildClass : ParentClass

{

public ChildClass( )

{ Console.WriteLine("子類構造函數。") ; }

public static void Main( ) {

ChildClass child = new ChildClass( ) ;

child.print( ) ;

}

}

  程式運作輸出:

  父類構造函數。子類構造函數。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的構造函數之前執行。

三. 通路與隐藏基類成員

  (1) 通路基類成員

  通過base 關鍵字通路基類的成員:

   調用基類上已被其他方法重寫的方法。

   指定建立派生類執行個體時應調用的基類構造函數。

   基類通路隻能在構造函數、執行個體方法或執行個體屬性通路器中進行。

   從靜态方法中使用 base 關鍵字是錯誤的。

  示例:下面程式中基類 Person 和派生類 Employee 都有一個名為 Getinfo 的方法。通過使用 base 關鍵字,可以從派生類中調用基類上的 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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

public class ParentClass

{

public ParentClass( )

{ Console.WriteLine("父類構造函數。"); }

public void print( )

{ Console.WriteLine("I'm a Parent Class。") ; }

}

public class ChildClass : ParentClass

{

public ChildClass( )

{ Console.WriteLine("子類構造函數。") ; }

public static void Main( ) {

ChildClass child = new ChildClass( ) ;

child.print( ) ;

}

}

  程式運作輸出:

  父類構造函數。子類構造函數。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的構造函數之前執行。

三. 通路與隐藏基類成員

  (1) 通路基類成員

  通過base 關鍵字通路基類的成員:

   調用基類上已被其他方法重寫的方法。

   指定建立派生類執行個體時應調用的基類構造函數。

   基類通路隻能在構造函數、執行個體方法或執行個體屬性通路器中進行。

   從靜态方法中使用 base 關鍵字是錯誤的。

  示例:下面程式中基類 Person 和派生類 Employee 都有一個名為 Getinfo 的方法。通過使用 base 關鍵字,可以從派生類中調用基類上的 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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

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() {

// 調用基類的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.派生類在初始化的過程中可以同基類進行通信。

  上面代碼示範了在子類的構造函數定義中是如何實作同基類通信的。分号":"和關鍵字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 不再生效。

  讓我們看下面的例子:

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

bstract class A

{

public abstract void F( ) ;

}

sealed class B: A

{

public override void F( )

{ // F 的具體實作代碼 }

}

  如果我們嘗試寫下面的代碼

class C: B{ }

  C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。

  (3) 密封方法

  我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。

  不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實作方法。是以,在方法的聲明中,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、引入類或結構中的常數、指定、屬性或類型隐藏具有相同名稱的所有基類成員。

   b、引入類或結構中的方法隐藏基類中具有相同名稱的屬性、字段和類型。同時也隐藏具有相同簽名的所有基類方法。

   c、引入類或結構中的索引器将隐藏具有相同名稱的所有基類索引器。

  注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隐藏繼承成員的聲明中使用 new 修飾符将發出警告。

  示例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 修飾符,程式将繼續編譯和運作,但您會收到以下警告:

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

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

四、多級繼承

  一些面向對象語言允許一個類從多個基類中繼承,而另一些面向對象語言隻允許從一個類繼承,但可以随意從幾個接口或純抽象類中繼承。

  隻有C++支援多級繼承,許多程式員對此褒貶不一。多級繼承常會引起繼承來的類之間的混亂,繼承而來的方法往往沒有唯一性,是以C#中類的繼承隻可以是一個,即子類隻能派生于一個父類,而有時你必須繼承多個類的特性,為了實作多重繼承必須使用接口技術,下面是對接口的多重繼承進行介紹:

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

using System ;

//定義一個描述點的接口

interface IPoint

{

int x {

get ;

set ;

}

int y {

get ;

set ;

}

}

interface IPoint2

{

int y {

get ;

set ;

}

}

//在point中繼承了兩個父類接口,并分别使用了兩個父類接口的方法

class Point:IPoint,IPoint2

{

//定義兩個類内部通路的私有成員變量

private int pX ;

private int pY ;

public Point(int x,int y) {

pX=x ;

pY=y ;

}

//定義的屬性,IPoint接口方法實作

public int x

{

get

{ return pX ; }

set

{ pX =value ; }

}

//IPoint1接口方法實作

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) ;

}

}

五、繼承與通路修飾符

  通路修飾符是一些關鍵字,用于指定聲明的成員或類型的可通路性。類的繼承中有四個通路修飾符: public protected internal private。使用這些通路修飾符可指定下列五個可通路性級别: public protected internal internal protected private。

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

聲明的可通路性 意義
public 通路不受限制。
protected 通路僅限于包含類或從包含類派生的類型。
internal 通路僅限于目前項目。
protected internal 通路僅限于從包含類派生的目前項目或類型。
private 通路僅限于包含類型。

  1、繼承中關于可通路域的一些問題

  基類的所有成員(執行個體構造函數、析構函數和靜态構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可通路域隻包括聲明該成員的類型的程式文本。在下面的示例中

class A

{

int x ;

static void F(B b) {

b.x = 1 ; // 對

}

}

class B: A

{

static void F(B b) {

b.x = 1 ; // 錯誤

}

}

  類 B 繼承類 A 的私有成員 x。因為該成員是私有的,是以隻能在 A 的"類體"中對它進行通路。是以,對 b.x 的通路在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。

  2、繼承中關于屬性的一些問題

  和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:

  屬性的重載

  1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。

  2. 在重載的聲明中,屬性的名稱、類型、通路修飾符都應該與基類中被繼承的屬性一緻。

  3. 如果基類的屬性隻有一個屬性通路器,重載後的屬性也應隻有一個。但如果基類的屬性同時包含get 和set 屬性通路器,重載後的屬性可以隻有一個,也可以同時有兩個屬性通路器。

  注意:與方法重載不同的是,屬性的重載聲明實際上并沒有聲明新的屬性,而隻是為已有的虛屬性提供通路器的具體實作。

  虛屬性

  1. 使用virtual 修飾符聲明的屬性為虛屬性。

  2. 虛屬性的通路器包括get 通路器和set 通路器,同樣也是虛的。

  抽象屬性

  1. 使用abstract 修飾符聲明的屬性為抽象屬性。

  2. 抽象屬性的通路器也是虛的,而且沒有提供通路器的具體實作。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對通路器的具體實作。

  3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的通路器也是抽象的。

  4. 抽象屬性隻允許在抽象類中聲明。

  5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。

  密封屬性

  1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的通路器同樣也是密封的。

  2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。

  從上面可以看出,屬性的這些規則與方法十分類似。對于屬性的通路器,我們可以把get 通路器看成是一個與屬性修飾符相同、沒有參數、傳回值為屬性的值類型的方法,把set 通路器看成是一個與屬性修飾符相同、僅含有一個value 參數、傳回類型為void 的方法。看下面的程式:

using System ;

public enum sex

{ woman, man, } ;

abstract public class People

{

private string s_name;

public virtual string Name

{

get

{ return s_name ; }

}

private sex m_sex ;

public virtual sex Sex

{

get

{ return m_sex ; }

protected string s_card;

public abstract string Card

{ get; set; }

}

  上面的例子中聲明了"人"這個類,人的姓名Name 和性别Sex 是兩個隻讀的虛屬性:身份證号Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,是以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程式:

class Customer: People

{

string s_no ;

int i_day ;

public string No

{

get

{ return s_no ; }

set

{

if (s_no != value)

s_no = value;

}

}

public int Day

{

get

{ return i_day ; }

set

{

if (i_day != value)

i_day = value;

}

}

public override string Name

{

get { return base.Name; }

}

public override sex Sex

{

get { return base.Sex }

}

public override string Card

{

get

{ return s_ card ; }

set

{ s_ card = value ; }

}

}

  在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一緻。Name 和Sex 的get 通路器,Card 的get 和set通路器都使用了base 關鍵字來通路基類People 中的通路器屬性。Card 的聲明重載了基類People 中的抽象通路器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。

3、繼承中對使用可通路性級别的限制

  聲明類型時,最重要的是檢視該類型是否必須"至少"與其他成員或類型"具有同樣的可通路性"。例如,直接基類必須至少與派生類具有同樣的可通路性。以下聲明将導緻編譯器錯誤,因為基類 BaseClass 的可通路性小于 MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

  下表彙總了對使用聲明的可通路性級别的限制。

上下文 備注
類類型的直接基類必須至少與類類型本身具有同樣的可通路性。
接口 接口類型的顯式基接口必須至少與接口類型本身具有同樣的可通路性。
委托 委托類型的傳回類型和參數類型必須至少與委托類型本身具有同樣的可通路性。
常數 常數的類型必須至少與常數本身具有同樣的可通路性。
字段 字段的類型必須與至少字段本身具有同樣的可通路性。
方法 方法的傳回類型和參數類型必須至少與方法本身具有同樣的可通路性。
屬性 屬性的類型必須至少與屬性本身具有同樣的可通路性。
事件 事件的類型必須至少與事件本身具有同樣的可通路性。
索引器 索引器的類型和參數類型必須至少與索引器本身具有同樣的可通路性。
運算符 運算符的傳回類型和參數類型必須至少與運算符本身具有同樣的可通路性。
構造函數 構造函數的參數類型必須至少與構造函數本身具有同樣的可通路性。

  示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋訓示了預期的編譯器錯誤。

using System ;

delegate int MyDelegate( ) ;

class B

{ // 定義一個私有的函數:

static int MyPrivateMethod()

{ return 0 ; }

}

public class A

{ // 字段定義:

public B myField = new B();// 錯誤: 類型B與A字段A.myField級别不同

// 構造函數:

public readonly B myConst = new B(); //錯誤: 類型B是僅讀的

//方法:

public B MyMethod()

{

return new B();

}

//屬性:

public B MyProp

{

set { }

}

public static B operator + (A m1, B m2)

{

return new B();

}

static void Main()

{

Console.Write("Compiled successfully");

}

}

繼續閱讀