泛型類繼承全解析

2021-05-22 04:54:01 字數 4031 閱讀 8466

泛型類之間的繼承

普通的繼承規則,包括成員的隱藏、過載和訪問限制等,原則上也都適用於泛型類之間的繼承。但由於泛型類是抽象的而非具體的資料型別,所以泛型類的繼承問題是乙個既有趣又容易產生混淆的問題。因為基類和派生類可能只有乙個是泛型類,也可能二者都是,本節將分別對這3種情況進行詳細說明。為了解說上的方便,首先需要引入開放型別和封閉型別的概念。

開放型別與封閉型別

在引入了泛型的概念之後,c#中的所有型別可以被劃分為兩部分:開放型別和封閉型別。

• 開放型別就是指含有型別引數的型別,包括:

型別引數本身;  以開放型別為元素型別的陣列型別;  包含開放型別的構造型別。

• 封閉型別則是指開放型別以外的所有型別。 這種遞迴的定義確實有些令人費解:當把乙個開放型別中所包含的型別引數都替換成為封閉型別時,該型別也就成為了乙個封閉型別。但從計算機的角度來理解,只有封閉型別才可以建立例項,並擁有記憶體儲存,而開放型別不是真正的資料型別。這裡「開放」的意思指的是具體的資料型別尚未最終確定。設s和t為型別引數,下列型別都屬於開放型別:

普通基類與派生泛型類

這應當說是泛型類繼承中最簡單的一種情況。像普通類一樣,派生的泛型類可以從基類繼承各種成員,隱藏基類中的同名成員,以及通過過載來實現多型性。至於派生類如何在自己的其他成員中使用型別引數來實現泛型的功能,這和基類沒有任何關係。惟一值得提醒的地方在於:不能用派生類中任何涉及到泛型的成員來過載基類中的成員。例如下面的**定義了乙個抽象類a: public abstract class a set }

//建構函式 public a(int ivalue)

//抽象方法

public abstract void fa(int ivalue); }

由於該抽象類中定義了抽象方法及帶引數的建構函式,其派生類就必須對抽象方法進行過載,並且定義具有相同形參的建構函式。再看下面定義的泛型類ga: /// /// 錯誤的派生泛型類 /// public class ga : a

} //建構函式 public ga(t tp) //error

//過載方法 public override void fa(t tp) //error }

上述**中,派生類ga對抽象類a的屬性、建構函式以及方法的過載都是錯誤的。儘管構造型別ga能夠達到過載的目的,但換成ga就會統統失敗。泛型類需要保證它的所有構造型別都能夠滿足基類的要求。對於上面的例子,所定義的成員可以保留,但要去掉不必要的override修飾,並且增加能夠滿足過載要求的成員定義: /// /// 泛型類:集合assemble /// public class ga : a } //建構函式 public ga(int ivalue) : base(ivalue)

public ga(t tp) : base(1) //方法 public override void fa(int ivalue) public void fa(t tp) }

泛型基類與普通派生類

由於開放型別不能從封閉型別中繼承,所以對非泛型類來說,它只能以泛型類的某個構造型別作為基類,而且該構造型別不能含有型別引數。

和前一種情況不同,由於基類和派生類都是封閉型別,基類對派生類的過載要求可以直接確定。例如下面的**定義了乙個泛型類ga,並為其構造型別ga定義了乙個派生類b: /// /// 泛型類 /// public abstract class ga } //建構函式 public ga(int ivalue) public ga(t tp) //抽象方法 public abstract void fa(t tp); } /// /// 派生類 /// public class b : ga } //建構函式 public b(int i) : base(i) //過載方法 public override void fa(int tp) }

此時派生類和基類都是封閉型別,派生類的所有成員,包括從基類中繼承的成員,當然也都要求是封閉型別。如果在泛型類中定義了開放型別的成員(這幾乎是肯定的),那麼需要通過作為基類的構造型別來決定派生類所繼承的成員。在上面的例子中,泛型類ga定義了乙個t型別的字段成員m_v1,那麼ga的派生類b所繼承的就是int型別的字段成員m_v1。

泛型基類與泛型派生類

最為複雜的一種情況莫過於基類和派生類都是泛型類了。在定義了乙個泛型類之後,

它自身以及它的某個構造型別都可能成為基類。例如,定義了泛型類ga之後,採用下面的方法來派生新的泛型類都是允許的: public class gb : ga public class gc : ga public class gd : ga 。

再涉及到多引數泛型類時,情況就更加混亂了。如果泛型類定義了多個型別引數,除了泛型類本身,其封閉的構造型別和開放的構造型別都可以作為其他泛型類的基類。例如,定義了泛型類a之後,採用下面的方法來派生新的泛型類都是允許的: public class b : a public class c : a public class d : a public class e : a public class f : a> public class g : a。

上面各個泛型類的型別限制都是相互關聯的。g1為型別引數t新增了new限制(即無參建構函式限制),而g2的基類為g1,就必須為型別引數s新增new限制;同樣,由於g3的繼承定義中出現了構造型別g2,而泛型類assemble中同時定義了icomparable介面限制和new限制,這種限制就必須傳遞給g3的型別引數r。

由於帶參建構函式和型別限制的原因,還可能導致泛型類的某些構造型別無法作為基類使用。例如下面的定義中,由於泛型類g1對型別引數t的限制,導致在g2的定義中t也要有帶參建構函式及預設建構函式,並繼承icomparable介面。但是陣列型別既不可能實現icomparable介面,也不可能提供建構函式,因此不論怎樣為s和t新增限制都無法滿足繼承要求: public class g1 where t : icomparable, new()

} public class g2 : g1 //error

在泛型類之間涉及到的繼承和多型性問題更加複雜。如果在泛型類中定義了開放型別的成員,那麼以泛型類本身作為基類時,派生類將直接繼承這些成員;而如果是以泛型類的某個構造型別作為基類,派生類所繼承的成員只是將相應的開放型別替換成為封閉型別。

在下面的**中,泛型類g2作為g1的派生類,它所繼承的字段和方法的型別就都成為t: public class g1

} public class g2 : g1

作為泛型類繼承的乙個例子,程式p10_6.cs試圖將程式p10_4.cs和p10_5.cs結合起來,希望能夠以乙個整數陣列為索引,對聯絡人集合進行方便的管理。由於c#不支援多繼承(幸虧如此,否則泛型類之間的多繼承會使得局面更加難以駕御),這裡考慮從程式p10_4.cs中定義的泛型類relation出發,擴充套件實現所需的功能。

//程式清單p10_6.cs: using system; using p9_6; using p10_4; namespace p10_6 } /// /// 泛型類:索引集合indexedassemble /// public class indexedassemble : p10_4.relation where t : ioutput,icomparable set } //建構函式 public indexedassemble(int ilength) : base(ilength) //方法 public void sort() } } } public void output() } }

派生的泛型類indexedassemble從構造型別relation中繼承,自然relation中定義的成員left就成為乙個整數陣列,該陣列在建構函式中進行初始化。sort方法中的**按照陣列欄位right中各元素的大小關係對left的元素進行排序,結果是在陣列left中按順序存放陣列right中對應元素的下標。例如,陣列right中最小的元素下標為2,則left[0]=2;次小的元素下標為5,則left[1]=5,依此類推。如果陣列right的元素型別是複雜的引用型別,則在整數陣列left中進行排序遠比直接在陣列right中進行排序效率要高。

順便提一句,sort方法中使用的是氣泡排序演算法,有興趣的讀者可以參考資料結構與演算法方面的專著。

對程式p10_6.cs進行編譯的命令為: csc /r:p10_4.exe /r:p9_6.dll p10_6.cs

執行程式,可以觀察到排序前後輸出內容的變化。

泛型 泛型類 泛型方法 泛型擦除

1 是什麼?一種允許我們在不確定引數型別時候使用的型別。例如我不知道a方法應該會傳string還是int,我就用個泛型先佔坑。2 為什麼要用泛型?泛型可以在編譯期自動確定具體型別,檢查型別是否匹配,可以提高 的重用率,減少冗餘編碼。3 泛型與object的區別?像上面說的我不知道方法a的引數型別,其...

帶有泛型的類如何繼承

public class genericitytst 具有泛型的classa public class classa 繼承泛型 public class classb extends classa 縮小泛型的範圍,是准許的,但是不允許擴大泛型的範圍 public class classc exten...

泛型之泛型類

public class a 構造引數型別上使用泛型 public a t t 方法返回值上使用泛型 public t gett 方法的引數上使用泛型 這是泛型類的方法,而不是泛型方法 public void sett t t 方法的返回值和引數型別上使用泛型 public t foo t t pu...