注冊會員,創建你的web開發資料庫, "一次編碼,多次使用",這就是引入泛型的根源。在以前的c++中稱為模板,c#泛型通過算法和數據結構支持獨立編碼。例如,泛型列表意味著,你不必再重寫一個強類型集合。在本文中,作者將向你展示定義和使用泛型是多么容易的事情-請注意,長期以來泛型一直被認為是最高級和最困難的術語。
一、 簡介
泛型現在在任何一種語言中都被認為是一個高級的強有力的術語。當我在c++中第一次接觸模板時,我對之有些疑惑。之后,我讀了bjarne stroustrop的《the design and evolution of c++》,才發現模板的使用就象c中的宏和用之來取代的簡單串替換模板一樣容易。其實,模板和泛型是相同的東西-盡管它們的實現稍微不同。
c#泛型支持在使用點處才定義算法及其數據類型。在c#的一些早期版本中,我們可以證明沒有泛型也可以工作,因為每種類型都是派生于一個公共基類型-object。這意味著程序員可以基于object類型定義一個棧類并且把一切東西放到該棧上(因為一切都派生于object)。然而,一個object棧意味著,customer對象,integer對象以及假想的對象都能被放置到同一個棧的實例上。結果是,開發者要子類化數據類型來把數據類型綁定到他們要與之交互的東西上去。例如,在編寫定制的商業對象時,我們就建議定義派生于system.collections.collectionbase的強類型集合。原因很簡單:基于object定義一切被認為是弱類型定義。
業界的高手們在數十年前就確信強類型優于弱類型,所以.net最終支持強類型,這看上去是很自然的事情。強類型算法當然建議類型化參數-這正是我們在泛型中所用的東西。
十幾年來,我們一直在使用字母t作為類型化參數的名字。這樣,在任何泛型類使用者所提供的數據類型的地方,你都能夠找到t。使用泛型的關鍵僅僅是提供這個t。定義泛型的關鍵在于實現一個方法或類,并且用特定數據類型來替換掉t。
c#中的泛型支持另外一些提煉。例如,一個方法或類可以有多個參數化的類型并且c#泛型還支持where約束-它用來具體要求類型化參數的類型。例如,如果一個泛型類型必須實現接口idisposable,那么c#泛型是支持實現這一限制的。在文章的最后我們還要看一下約束問題。
閑話少說,讓我們言歸正傳。
二、 使用泛型集合
有些人問我"面向對象編程(oop)的承諾在哪里?",我的回答是應該從兩個方面來看oop:你所使用的oop和你創建的oop。如果我們簡單地看一下如果沒有如例如microsoft的.net,borland的vcl,以及所有的第三方組件這樣的oo框架,那么很多高級的應用程序幾乎就無法創建。所以,我們可以說oop已經實現了它的承諾。不錯,生產好的oop代碼是困難的并且可能是極具挫敗性的;但是記住,你不必須一定要通過oop來實現你的目標。因此,下面首先讓我們看一下泛型的使用。
當你用visual studio或c# express等快速開發工具創建工程時,你會看到對于system.collections.generic命名空間的參考引用。在這個命名空間中,存在若干泛型數據結構-它們都支持類型化的集合,散列,隊列,棧,字典以及鏈表等。為了使用這些強有力的數據結構,你所要做的僅是提供數據類型。
列表1顯示出我們定義一個強類型集合的customer對象是很容易的。
列表1 這個控制臺應用程序包含一個customer類和一個基于list<t>的強類型集合customers。
using system;
using system.collections.generic;
using system.text;
namespace generics{
class program{
static void main(string[] args){
list<customer> customers = new list<customer>();
customers.add(new customer("motown-jobs"));
customers.add(new customer("fatman's"));
foreach (customer c in customers)
console.writeline(c.customername);
console.readline();
}
}
public class customer{
private string customername = "";
public string customername{
get { return customername; }
set { customername = value; }
}
public customer(string customername){
this.customername = customername;
}
}
}
注意,我們有一個強類型集合-list<customer>-對這個集合類本身來說不需要寫一句代碼。如果我們想要擴展列表customer,我們可以通過從list<customer>繼承而派生一個新類。
三、 實現一個泛型類
一種合理的實現某種新功能的方法是在原有的事物上進一步構建。我們已經了解強類型集合,并知道一種不錯的用來構建泛型類的技術是使用一個特定類并刪除數據類型。也就是說,讓我們定義一個強類型集合customerlist,并且來看一下它要把什么東西轉化成一個泛型類。
列表2定義了一個類customerlist。后面的部分把customerlist轉化成list<t>。
列表2定義類customerlist:
using system;
using system.collections;
using system.text;
namespace generics{
public class customerlist : collectionbase{
public customerlist() { }
public customer this[int index]{
get { return (customer)list[index]; }
set { list[index] = value; }
}
public int add(customer value)
{return list.add(value);}
}
}
四、 定義類頭
如果我們定義一個泛型類,我們需要把類頭轉化成一個泛型類。所有我們需要做的是命名參數并且把類名改成某種泛型。list<t>只有一個參數t,并且因為我們在以一種向后兼容的方式工作,所以我們知道類名是list。列表3顯示出列表2中類的新類頭。
列表3 一個泛型類頭顯示出參數化的參數t。
using system;
using system.collections;
using system.text;
namespace generics{
public class list<t> : collectionbase {}
五、 實現泛型字段
如果我們需要把任何字段轉換成泛型字段,我們將只需簡單地把它們的類型改變成t(或該字段所描述的任何參數)。泛型list不需要任何字段,但是假定存在一個私有的整型字段叫foo-我們將把它泛型化。我們將如下重新定義它:
private t foo;
當參數t被填充到類中時,list t也將因foo被填充。
六、 定義泛型方法
接下來,我們為所需要的參數化類型定義其它一些特性。這包括屬性,方法,和事件。在我們的實例中,在customer出現的每一處,我們都用參數t替換它。完成后的泛型列表類顯示于列表4中。
列表4 一個基于system.collections.collectionbase的輕量級的參數化泛型列表類。
using system;
using system.collections;
using system.text;
namespace generics{
public class list<t> : collectionbase {
public list(){ }
public t this[int index] {
get { return (t)list[index]; }
set { list[index] = value; }
}
public int add(t value) {
return list.add(value);
}
}
}
為了測試該定制列表,注釋掉使用system.collections.generic命名空間一句并且把列表4中的list<t>使用在列表1的代碼中;它將以同樣的方式工作。
全面地修改.net的list<t>是不必要的而且它也包含遠比我們的示例多得多的特性;但是列表4顯示出這種機制對于定義定制泛型類是多么容易。 七、 增加類型約束
最后要討論的是約束問題。約束被應用于類或其它特性上并且使用下面的語法:
where t : constraint_type
例如,任何我們想要通過using語句所使用的,如一個sqldatareader,必須實現idisposable接口。這是因為如下方式使用的using語句:
using(form f = new form()){...}
就象一個try..finally塊一樣工作-總是清除新創建的資源。其工作原理很簡單,只需要clr針對在該using語句中創建的對象發出一個到idisposable.dispose的調用即可。例如,在上面這一句中,一個新的表單被創建,并且在using語句退出之前即調用form.dispose。
要對一個泛型類施加以確保該類實現了接口idisposable,我們將添加先行詞where t:idisposable。在列表4中的泛型列表上施加約束后,我們將重新修改列表4如下面的列表5所示。
列表5 增加一個約束到泛型類以確保我們的list<t>中的所有的值t實現接口idisposable。
using system;
using system.collections;
using system.text;
namespace generics{
public class list<t> : collectionbase where t : idisposable{
public list(){ }
public t this[int index]{
get { return (t)list[index]; }
set { list[index] = value; }
}
public int add(t value){return list.add(value);}
}
}
先行詞where的值可以是類,接口,結構,實現一個無參的公共構造器或有一個特定的基類的類。詳見有關幫助文檔。
八、 總結
泛型的設計是用來減少你重復實現的代碼的次數-只需改變數據類型即可。因為抽象數據結構,如隊列,棧和列表皆是典型的數據結構,所以存在針對這些東西的泛型類完全可以理解。你可以從.net中派生大量的值-通過使用現有的泛型類,如在system.collections.generic命名空間中的那些。
可以肯定,在一段相當長的時間里,泛型將會象模式和重構等革新一樣對開發帶來越來越大的價值,而且新的數據結構能被轉換成可重用的如泛型等的代碼元素。