標籤:
日常生活中的事物都是有類型的,比如我們說“一個女人”,那麼“女”就是這個人的類型。我們可以說“女人都是水做的”,那麼聽者都知道這是在說“女”這種類型的人。再比如你去肉店買肉,你可以對老闆說“我要十斤豬肉”,那麼老闆一定知道你是在要“豬”這種類型的肉。
日常生活中的這些語言都是帶有類型的,但是在日常生活中還有一些語言是不帶類型的。比如我們經常說“人是貪婪的”,這裡的人就沒有類型之分,聽者都知道是指所有的人;我們也可以在肉店裡指著豬肉說“給我來十斤肉”,肉店老闆同樣知道你要的是豬肉。
程式語言必須能夠對現實中的資料進行表述,對於C#語言來講可以使用資料類型對資料進行精確的描述。事實上這種程式語言被稱作強型別語言,在這樣的語言當中出現的資料都必須帶有資料,這樣的語言還有很多,比如C++、Java、Python等。與強型別語言對應的得是弱類型語言,比如VB、JavaScript等,他們沒有資料類型概念。從肉店買肉這個例子我們可以看出這兩種類型的各自的優缺點。
強型別語言顯然可以精確的表達邏輯但表達過於羅嗦,無論是肉店老闆還是旁邊的人聽到“我要十斤豬肉”這句話都可以精確的知道你的意思。弱類型語言的特點就是表達簡潔但邏輯容易發生混亂,比如你還可以指著豬肉說“來十斤”,很顯然你的話只有肉店老闆先看懂你的手勢才能懂,容易引起邏輯的混亂。
電腦程式是推理性語言,中間某一行邏輯出錯都會導致最終的結果出現錯誤,所以從這個角度出發,顯然在買豬肉這個問題上強型別語言獲勝。我們再來看關於人的那個表述,對於“人是貪婪的”這句話,是在描述一種通用性的規律。
對於這個問題用傳統的強型別語言來描述就是“女人是貪婪的,男人是貪婪的”,這樣說顯然非常囉嗦,這也是強型別語言都存在一個缺陷。比如在程式中經常會用到某些通用的演算法,用強型別語言編寫這些通用的演算法會和上面出現一樣的情況,需要每種資料類型都提供一個相同的演算法。泛型技術就是用可以用來解決此類問題。
重點:
Ø 理解泛型的概念
Ø 泛型的定義及其應用
Ø 泛型類
預習功課:
Ø 泛型的概念
Ø 如何定義泛型及其應用
Ø 如何使用泛型類
9.1 為什麼使用泛型
假如讓你用C#編寫一個求兩個數和的方法,你會怎麼做?若求的兩個數是整數,可以定義如下方法:
C#
int Add(int a,int b)
{ return a+b; }
若求的是兩個double型的數的和,可以定義如下方法:
C#
static double Add(double a,double b)
{ return a+b; }
若是字串型的數值進行相加,那麼你就可以定義如下方法:
C#
static double Add(string a,string b)
{
return double.Parse(a)+double.Parse(b);
}
假如有一天程式需要升級,你需要其他資料類型求和的演算法,日不char、long、decimal等,那你怎麼辦?繼續重載嗎?還是想一個更好更通用的方法?我們可能會想到使用object類,於是你寫了下面這個通用的演算法:
C#
staticobject Add(object a,object b)
{
//decimal為最大的數實值型別,所以使用它
return decimal.Parse(a)+decimal.Parse(b);
}
static voidMain(string[]args)
{
decimal r1=(decimal)Add(3,3);
decimal r2=(decimal)Add(3.3,3.3);
decimal r3=(decimal)Add("3.3","3.3");
Console.WriteLine("{0},{1},{2}",r1,r2,r3) ;
}
staticobject Add(object a,object b)
{
returnConvert.ToDecimal(a)+Convert.ToDecimal(b);
}
運行結果:
6,6.6,6.6
這裡用到的技術就是裝箱和拆箱,Add方法首先將所有資料類型的資料進行裝箱,這樣就統一了它們的類型,然後再進行類型轉換和計算,計算結果再拆箱就是要求的結果。實際上就是“泛型”思想的一個應用,這裡用一個通用方法解決了幾乎任何數實值型別兩個數的求和操作。所以可以說,對於這個求和演算法來講是通用的、泛型的(不需要特定資料類型)。
但是我們從上面的字碼頁可以看到問題,就是它執行了頻繁的裝箱和拆箱操作,我們知道這些操作是非常損耗效能的。另外,裝箱和拆箱的代碼也顯得比較“難看”
因為每次都要進行強型別轉換,有沒有更好的方式讓我們編寫這種通用演算法呢?於是,C#從2.0版本開始引入了泛型技術,泛型能夠給我們帶來的兩個明顯好處是—代碼清晰和減少了裝箱、拆箱。
9.2 C#泛型簡介
利用泛型解決交換兩數的泛型方法的例子:
C#
using System;
class Program
{
static void Main(string[]args)
{
int i=1,j=2;
Console.WriteLine("交換前:{0},{1}",i,j);
Swap(ref I,ref j); //交換兩個數
Console.WriteLine("交換後:{0}",i,j);
}
//交換兩個數的泛型演算法
static void Swap(ref T a,ref T b)
{
T temp=a ;
a=b ;
b=temp ;
}
}
運行結果:
交換前:1,2
交換後:2,1
這個交換演算法不僅支援任何數字類型,它還支援你在程式中能用到得任何類型。注意,泛型不屬於任何命名空間,準確的講,泛型是一種編譯技術。在書寫演算法的時候,泛型技術允許我們使用一種類型預留位置(或稱之為型別參數,這裡使用的預留位置是“T”)作為類型的標識符,而不需要指定特定類型。
當我們在調用這個演算法的時候,編譯器使用指定的類型代替類型預留位置建立一個針對這種類型的演算法。這就是泛型技術,它允許你編寫演算法的時候不指定具體類型,但調用的時候一定要指定具體類型,編寫演算法的時候使用“<>”來指定類型預留位置,調用的時候一般也使用“<>”來指定具體的資料類型。
上面這個例子中的Swap,指定了這個泛型方法的預留位置是“T”,指定後我們就可以認為有了這麼一個資料類型,該類型就是T類型,然後這個T類型既可以作為參數的資料類型又可以作為方法的傳回值類型,還可以在方法內部作為局部變數的資料類型。當我們通過Swap(ref i,ref j)來調用這個泛型方法時,在編譯時間Swap方法中所有出現“T”的地方都會被“int”類型所代替,也就相當於我們建立了
int型的交換方法,如:
C#
static void Swap(ref int a,ref int b)
{
int temp=a;
a=b;
b=temp;
}
l 代碼重用
泛型最突出優點就是可以代碼重用。從上面舉的交換演算法的例子你也可以看出節省了多少代碼。對於一個程式員來講,寫的好的演算法是很重要的財富,例如我們一直在使用各種類庫,這些類庫實際上就是一些優秀的程式員封裝的,我們直接調用就是一個代碼重用的過程。
l 型別安全
型別安全的含義是類型之間的操作必須是相容的,反之就是類型不安全。類型不安全的代碼會在運行時出現異常,比如兩個數相加的演算法,Convert.ToDecimal(a),a是object類型,a可以是數值“3.3”,a也可以是一般字元串“hello”,如果a是後者那麼執行類型轉換時必定會出異常,所以說使用Convert.ToDecimal(a)是類型不安全的做法,同樣那個求和的方法也是類型不安全的方法。泛型本質上還是強型別的,如果你使用一個不相容的類型來調用泛型演算法,編譯器是會報錯的,所以說泛型是型別安全的。
l 效能更佳
相比裝箱和拆箱,泛型效率更高一些。因裝箱時系統需要分配記憶體,而拆箱時需要類型轉換,這兩個操作都是極其耗費效能的。特別是在執行一些大資料量的演算法時(比如排序、搜尋等)裝箱和拆箱效能損耗尤其嚴重,因此,在C#中提倡使用泛型。
9.3 泛型定義及其應用
使用泛型可以定義泛型方法、泛型類、泛型介面等。在這些泛型結構的定義中,泛型型別參數(或叫預留位置)是必須指定的,型別參數所包含的類型就是我們定義的泛型型別,我們可以一次性定義多個泛型型別,如泛型方法Swap三個泛型型別。型別參數一般放在所定義的類、方法、介面等標識符後面,並且包含在“<>”裡面。
泛型型別名稱的寫法也有一定的規則:
l 泛型型別名稱必須是由字母、數字、底線組成,並且必須以字元或底線開頭。比如_T、T、TC都是有效泛型型別名稱。
l 務必使用有意義泛型型別名稱,除非單個字母名稱完全可以讓人瞭解它表示的含義,如T.
l 當型別參數裡只有單個泛型型別時,考慮使用T作為泛型型別名,如class Note。
l 提倡作為泛型型別名的首碼,如Tkey,TValue。
前面舉例子的時候,一般使用了泛型型別T,但從本質上講我們可以使用滿足上面要求的任何單詞。實際上,泛型型別名和類名或介面名的定義規則基本一樣。
9.4 泛型結構體
結構是實值型別,通常可以定義結構類型來表示一些簡單的對象。比如,我們前面接觸的系統結構體Point、DateTime等。但是,這些結構體通常都儲存一種類型的資料,我們可以定義一個泛型結構體,它將可以儲存任何資料,定義規則:
C#
struct 結構名 <泛型型別列表>
{
結構體;
}
要注意泛型型別標識符的定義只能放在結構名的後面,下面我們定義了一個
Point類型的泛型結構體,此時該結構體的X、Y可以儲存任何數實值型別的座標資料。代碼如下:
C#
classProgram
{
//定義泛型結構體和泛型型別T
struct Point
{
public T X;
public T Y;
}
//測試泛型結構體
static voidMain(string[]args)
{
//給T類型指定資料類型為int型
Point a =newPoint();
X=1;
a. Y=2;
Console.WriteLine("{0},{1}",a.X,a.Y);
}
}
運行結果:
1,2
9.5 泛型類
泛型類封裝不屬於特定具體資料類型的資料或操作。泛型類最常見的就是泛型集合類,如鏈表、雜湊表、堆棧、隊列、樹等。對於集合的操作,如從集合中添加、移除、排序等操作大體上都以相同方式進行的,與所儲存資料類型無關,即可使用泛型技術。
在泛型類中使用的資料類型,可以是泛型型別也可以是普通的。一般規則是,類中使用的泛型型別越多,代碼就會變得越靈活,重用性就越好。但是要注意,類中如果有太多的泛型型別也會使其他開發人員難以閱讀或理解該類。要定義類的泛型型別也是在類名後面通過"<>"定義,類的其他元素除了方法外都不能定義自己的泛型型別,但可以使用該類定義的泛型型別。泛型類定義規則如下:
C#
class 類名<泛型型別列表>
{
//類體
}
//範例程式碼:
usingSystem;
classProgram
{
//定義泛型類和泛型型別T
private class Node
{
private T data;
public Node(T t)
{
data=t;
}
public T Data
{
get{return data;}
set{data=value;}
}
}
static void Main()
{
Nodenode=newNode(10000);
Console.WriteLine("資料:{0}",node.Data);
Nodesnode=newNode("壹萬");
Console.WriteLine("資料:{0}",snode.Data);
}
}
運行結果:
資料:10000
資料:壹萬
如前所述,類中的成員有很多,如欄位、屬性、方法、事件、索引器等,其中除了方法之外,其他的類成員都不能自訂的泛型型別,只能使用定義類的時候定義的泛型型別或系統資料類型:
C#
classStudent
{
private T name; //姓名
private U[]score; //各個科目的分數數組
private int ucode; //編號使用系統資料類型
public U this[int n] //返回一個分數
{
get{return score[n];
}
}
類中的方法可以是泛型的,泛型方法的定義規則如下:
存取修飾詞 傳回型別 方法名<泛型型別列表>(方法參數列表)
如:
C#
public voidShow(T a){}
此泛型方法的使用時要給T指定一個實際的資料類型,如:
C#
Show("hello");
其中方法的泛型型別列表中定義的泛型型別可以出現在方法的任何位置,包括傳回值、參數、方法內,當然也可以不出現,比如下面這些都是合法的:
C#
public TGet(T a) {return default(T) ;}
public intGet
public TGet(int a) {return default(T);}
這上面用了default關鍵字,這個關鍵字可以取當前類型的預設初始值,這個關鍵字對於參考型別會返回null,對於數實值型別會返回零。
另外,類中也可以出現泛型的重載方法,如:
C#
voidDoWork(){}
voidDoWork(){}
voidDoWork(){}
由於方法是在類中,所以泛型方法中的資料類型又三種情況,一種是類的泛型型別,一種是泛型方法自身的泛型型別,另外還可以是系統資料類型。泛型方法和非泛型方法或屬性、索引器可以互相調用。如:
C#
classStudent
{
private U id;
private string name;
public void ShowHello()
{
this.Show("hello"); //調用泛型方法
}
public void ShowId()
{
this.Show(id);
}
private void Show(S msg)
{
Console.WriteLine(msg);
}
}
類的泛型型別只能用於本類,方法的泛型型別只能用於本方法。不管誰定義的泛型,一旦定義了泛型型別,你可以就當泛型型別是一個真實的類型來用了。
9.6 典型的泛型類
.Net架構類庫中,System.Collections.Generic和System.Collections.ObjectModel命名空間中,分別定義了大量的泛型類和泛型介面,這些泛型類多為集合類,因為泛型最大的應用正體現於再集合中對於不同類型對象的管理。
下表列出了,.Net架構中常用的泛型類和泛型介面:
泛型類說明
List對應於ArrayList集合類,可以動態調整集合容量,通過索引方式訪問對象,支援排序、搜尋和其他常見操作。
SortedList對應於SortedList集合類,表示Key/Value對集合,類似於SortedDictionary集合類,而SortedList在記憶體上更有優勢。
Queue對應於Queue集合類,是一種先進先出的集合類,常應用於順序儲存處理。
Stack對應於Stack集合類,是一種後進先出的集合類。
Collection對應於CollectionBase集合類,是用於自訂泛型集合的基類,提供了受保護的方法來實現定製泛型集合的行為Collection的執行個體是可修改的。
Dictionary對應於Hashtable集合類,表示Key/Value對的集合類,Key必須是唯一的,其元素類型既不是Key的類型,也不是Value的類型,而是KeyValuePair類型。
C#基礎:泛型的理解和使用