8.2.1 介面
介面是把隱式公用方法和屬性群組合起來,以封裝特定功能的一個集合。定義介面後,可以在類中實現它,以便支援介面所指定的所有屬性和成員。可以把較一般用途的屬性和方法組合到一個介面中,然後在類中使用該介面。
注意,介面不能單獨存在。不能像執行個體化一個類那樣執行個體化介面。另外,介面不能包含實現其成員的任何代碼,而只能定義成員本身。實現過程必須在實現介面的類中實現。
支援IDisposable介面的對象必須實現其Dispose()方法,即它們必須提供這個方法的代碼。當不再需要某個對象時,調用該方法釋放重要的資源,否則該資源會等到記憶體回收調用解構函式時才釋放。
C#允許使用一種可以最佳化使用Dispose()方法的結構。Using關鍵字可以在代碼塊中初始化使用重要資源的對象,Dispose()方法會在這個代碼塊的末尾自動調用,用法如下:
<ClassNme> <VariableName> = new <ClassName>()
….
Using(<VariableName>)
{
….
}
或者把初始化對象<VariableName>作為using語句的一部分:
Using(<ClassName> <VariableName> = new <ClassName>())
{
….
}
在這兩種情況下,變數<VariableName>可以在using代碼塊中使用,並在代碼塊的末尾自動刪除(在代碼塊執行完畢後,調用Dispose())。
8.2.2 繼承
繼承是OOP最重要的特徵之一。任何類都可以從另一個類中繼承,即這個類擁有它所繼承的類的所有成員。在OOP中,被繼承(也稱為派生)的類稱為父類(也稱為基類)。注意C#的對象僅能直接派生於一個基類,當然基類也可以有自己的基類。
繼承性可以從一個較一般的基類擴充或建立更多的特定類。例如,考慮一個代表家禽的類。這個類叫做Animal,擁有方法如EatFood()或Breed(),我們可以建立一個衍生類別Cow,支援所有這些方法,它也有自己的方法,如Moo()和SupplyMilk()。還可以建立另一個衍生類別Chicken,該類有Cluck()和LayEgg()方法。
在繼承一個基類時,成員的可訪問性就成了一個重要的問題。衍生類別不能訪問基類的私人成員,但可以訪問其公用成員和保護(protected)成員。
除了定義成員的保護層級外,還可以為成員定義其繼承行為。基類的成員可以是虛擬,也就是說,成員可以由衍生類別重寫。衍生類別可以在基類的基礎上提供成員的其他執行代碼。這種執行代碼不會刪除原來的代碼,仍可以在類中訪問原來的代碼,但外部代碼只能訪問衍生類別提供的執行代碼。如果沒有提供其他執行方式,外部代碼就訪問基類中成員的執行代碼。
基類還可以定義為抽象類別。抽象類別不能直接執行個體化。要使用抽象類別,必須繼承這個類,抽象類別可以有抽象成員,這些成員在基類中沒有執行代碼,這些執行代碼必須在衍生類別中提供。抽象基類可以提供成員的實現代碼,這是很常見的。不能執行個體化抽象類別,並不意味著不能在抽象類別中封裝功能。
密封類不能用作基類。
8.2.3 多態性
繼承的一個結果是派生於基類的(若干個)類在方法和屬性上有一定的重疊。因此,可以使用相同的文法處理從同一個基類繼承的不同類的對象。例如,如果基類Animal有一個方法EatFood(),則從派生於它的類Cow和Chicken中調用這個方法,其文法是類似的:
Cow myCow = new Cow();
Chicken myChicken = new Chicken();
myCow.EatFood();
myChicken.EatFood();
多態性則更推進了一步。可以把衍生類別的變數賦予基類的變數,例如:
Animal myAnimal = myCow;
不需要進行強制類型轉換,就可以通過這個變數調用基類的方法:
MyAnimal.EatFood();
結果是調用衍生類別中的EatFood()執行代碼。注意,不能以相同的方式調用衍生類別上定義的方法,下面的代碼不能運行:
myAnimal.Moo();
但是,可以把基類的變數轉換為衍生類別變數,調用衍生類別的方法,如下所示:
Cow myNewCow = (Cow)myAnimal;
myNewCow.Moo();
如果原始變數的類型不是Cow所繼承的基類,這個強制類型轉換就會產生一個異常。
注意並不是只有共用同一個父類的類才能利用利用多態性。只要子類和孫子類在繼承階層中有一個相同的類,它們就可以用相同的方式利用多態性。還要注意,在C#中,所有的類都派生於同一個類object,object是繼承階層中的根。
介面的多態性
介面概念是組合相關的屬性和方法。儘管不能像對象那樣執行個體化介面,但可以建立介面類型的變數,然後就可以在支援該介面的對象上,使用這個變數訪問該介面提供的方法和屬性。
假如,假定不使用基類Animal提供的EatFood()方法,而是把該方法放在介面IConsume上。Cow和Chicken類也支援這個介面,唯一的區別是它們必須提供EatFood()方法的執行代碼(因為介面不包含執行代碼),接著就可以使用下述代碼訪問該方法了:
Cow myCow = new Cow();
Chicken myChicken = new Chicken();
IConsume consumeInterface;
consumeInterface = myCow;
consumeInterface.EatFood();
consumeInterface = myChicken;
consumeInterface.EatFood;
這就提供了以相同方式訪問多個對象的方式且不依賴於一個公用的基類。
注意,衍生類別會繼承其基類支援的介面。有共同基類的類不一定有共同的介面,反之亦然。
8.2.4 對象之間的關係
內含項目關聯性:一個類包含另一個類。這類似於繼承關係,但包含類可以控制對被包含類的成員的訪問,甚至在使用被包含類的成員進行其他處理。
集合關係:一個類用作另一個類的多個執行個體的容器。這類似於對象數組,但集合有其他功能,包括索引、排序、重新設定大小等。
1.內含項目關聯性
把某個類聲明為成員欄位,就可以實現內含項目關聯性。這個成員欄位可以是公用欄位,此時與繼承關係一樣,容器物件的使用者可以訪問它的方法和屬性,但不能像繼承關係那樣,通過衍生類別訪問類的內部代碼。也就是說,使用者只能訪問被包含類的公用屬性和方法。
另外,可以讓被包含的成員對象變成私人成員。如果這麼做,使用者就不能直接存取其成員,即使這些成員是公用的,也不能訪問。但可以使用包含類的成員(間接)訪問這些私人成員。在訪問被包含類的成員前,可以在包含類的成員上進行其他處理。
例如,Cow類包含一個Udder類,它有一個公用方法Milk。Cow對象可以按照要求調用這個方法,作為其SuplyMilk()方法的一部分,但Cow對象的使用者看不到這些細節。
在UML中,被包含類可以用關聯線條來表示。對於簡單的內含項目關聯性,可以用帶有1的線條說明一對一的關係。
2.集合關係
使用數組儲存相同類型的多個變數,這也適用於對象。例如:
Animal[] animals = new animal[5];
集合基本上是數組,集合以與其它對象相同的方式實現為類。它們通常以所儲存的對象名稱的複數形式來命名,例如類Animals包含Animal對象的一個集合。
數組與集合的主要區別是,集合通常執行額外的功能,例如Add()和Remove()方法可以添加和刪除集合中的項。而且集合通常有一個Item屬性 ,它根據對象的索引返回該對象。不僅如此,這個屬性還允許以更複雜的訪問方式實現。例如,如果設計一個Animals,Animal對象就可以根據其名稱來訪問。
8.2.5 運算子多載
可以把運算子用於從類執行個體化而來的對象,因為類可以包含運算子如何運算的指令。
例如,給Animal添加一個新屬性Weight。接著使用下述代碼比較家禽的重量:
If(cowA.Weight > cowB.Weight)
{
...
}
使用運算子多載,可以在代碼隱式使用屬性Weight的邏輯,例如下面的代碼:
If(cowA > cowB)
{
...
}
也可以重載運算子,以相同的方式處理不同的類,其中一個(或兩個)類定義包含達到這一目的的代碼。
注意只能使用這種方式重載現有的C#運算子,不能建立新的運算子。
8.2.6 事件
對象可以啟用事件,作為它們處理的一部分。事件是非常重要的,可以在代碼的其他部分起作用,類似於異常(但功能更強大)。例如,可以在把Animal對象添加到Animals集合時,執行特定的代碼,而這部分代碼不是Animals類一部分,也不是調用Add()方法的代碼的一部分。為此,需要給代碼添加事件處理常式,這是一種特殊類型的函數,在事件發生時調用。還需要配置這個處理常式,以監聽我們感興趣的事件。
8.2.7 參考型別和實值型別
在C#中,資料根據變數的類型以兩種方式中的一種儲存在一個變數中。變數的類型分為兩種:參考型別和實值型別。其區別如下:
- 實值型別在記憶體的一個地方(稱為堆棧)儲存它們自己和它們的內容。
- 參考型別在記憶體的一個地方(稱為堆)儲存一個引用,而在另一個地方儲存內容。
實值型別和參考型別的一個主要區別是,實值型別總是包含一個值,而參考型別可以是null,表示它們不包含值。但是,可以使用可空類型(這是泛型的一種形式)建立一個實值型別,是實值型別在這個方面的行為類似於參考型別(即可以為null)。
類就是參考型別。
結構類型和類的關鍵區別是,結構類型是實值型別。