Linq系列—泛型

轉載之泛型的理解和使用

日常生活中的事物都是有類型的,比如我們說“一個女人”,那麼“女”就是這個人的類型。我們可以說“女人都是水做的”,那麼聽者都知道這是在說“女”這種類型的人。再比如你去肉店買肉,你可以對老闆說“我要十斤豬肉”,那麼老闆一定知道你是在要“豬”這種類型的肉。

日常生活中的這些語言都是帶有類型的,但是在日常生活中還有一些語言是不帶類型的。比如我們經常說“人是貪婪的”,這裏的人就沒有類型之分,聽者都知道是指所有的人;我們也可以在肉店裏指着豬肉說“給我來十斤肉”,肉店老闆同樣知道你要的是豬肉。

程序語言必須能夠對現實中的數據進行表述,對於C#語言來講可以使用數據類型對數據進行精確的描述。事實上這種程序語言被稱作強類型語言,在這樣的語言當中出現的數據都必須帶有數據,這樣的語言還有很多,比如C++、Java、Python等。與強類型語言對應的得是弱類型語言,比如VB、JavaScript等,他們沒有數據類型概念。從肉店買肉這個例子我們可以看出這兩種類型的各自的優缺點。

強類型語言顯然可以精確的表達邏輯但表達過於羅嗦,無論是肉店老闆還是旁邊的人聽到“我要十斤豬肉”這句話都可以精確的知道你的意思。弱類型語言的特點就是表達簡潔但邏輯容易發生混亂,比如你還可以指着豬肉說“來十斤”,很顯然你的話只有肉店老闆先看懂你的手勢才能懂,容易引起邏輯的混亂。

計算機程序是推理性語言,中間某一行邏輯出錯都會導致最終的結果出現錯誤,所以從這個角度出發,顯然在買豬肉這個問題上強類型語言獲勝。我們再來看關於人的那個表述,對於“人是貪婪的”這句話,是在描述一種通用性的規律。

對於這個問題用傳統的強類型語言來描述就是“女人是貪婪的,男人是貪婪的”,這樣說顯然非常囉嗦,這也是強類型語言都存在一個缺陷。比如在程序中經常會用到某些通用的算法,用強類型語言編寫這些通用的算法會和上面出現一樣的情況,需要每種數據類型都提供一個相同的算法。泛型技術就是用可以用來解決此類問題。
重點:
理解泛型的概念
泛型的定義及其應用
泛型類

1 爲什麼使用泛型
假如讓你用C#編寫一個求兩個數和的方法,你會怎麼做?若求的兩個數是整數,可以定義如下方法:

int Add(int a,int b)

{ return a+b;  }

若求的是兩個double型的數的和,可以定義如下方法:

static  double Add(double a,double b)

{   return a+b;    }

若是字符串型的數值進行相加,那麼你就可以定義如下方法:

static  double Add(string a,string b)

{
return double.Parse(a)+double.Parse(b);

}

假如有一天程序需要升級,你需要其他數據類型求和的算法,日不char、long、decimal等,那你怎麼辦?繼續重載嗎?還是想一個更好更通用的方法?我們可能會想到使用object類,於是你寫了下面這個通用的算法:

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版本開始引入了泛型技術,泛型能夠給我們帶來的兩個明顯好處是—代碼清晰和減少了裝箱、拆箱。

2 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型的交換方法,如:

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#中提倡使用泛型。
3 泛型定義及其應用
使用泛型可以定義泛型方法、泛型類、泛型接口等。在這些泛型結構的定義中,泛型類型參數(或叫佔位符)是必須指定的,類型參數所包含的類型就是我們定義的泛型類型,我們可以一次性定義多個泛型類型,如泛型方法Swap

struct 結構名 <泛型類型列表>

{

結構體;

}

要注意泛型類型標識符的定義只能放在結構名的後面,下面我們定義了一個
Point類型的泛型結構體,此時該結構體的X、Y可以保存任何數值類型的座標數據。代碼如下:

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

5 泛型類

泛型類封裝不屬於特定具體數據類型的數據或操作。泛型類最常見的就是泛型集合類,如鏈表、哈希表、堆棧、隊列、樹等。對於集合的操作,如從集合中添加、移除、排序等操作大體上都以相同方式進行的,與所存儲數據類型無關,即可使用泛型技術。

在泛型類中使用的數據類型,可以是泛型類型也可以是普通的。一般規則是,類中使用的泛型類型越多,代碼就會變得越靈活,重用性就越好。但是要注意,類中如果有太多的泛型類型也會使其他開發人員難以閱讀或理解該類。要定義類的泛型類型也是在類名後面通過"<>"定義,類的其他元素除了方法外都不能定義自己的泛型類型,但可以使用該類定義的泛型類型。泛型類定義規則如下:

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

數據:壹萬

如前所述,類中的成員有很多,如字段、屬性、方法、事件、索引器等,其中除了方法之外,其他的類成員都不能自定義的泛型類型,只能使用定義類的時候定義的泛型類型或系統數據類型:

classStudent<T,U>

{

private T name;      //姓名

private U[]score;    //各個科目的分數數組

private int ucode;   //編號使用系統數據類型

public U this[int n]  //返回一個分數

{

get{return score[n];

}

}

類中的方法可以是泛型的,泛型方法的定義規則如下:

訪問修飾符 返回類型 方法名<泛型類型列表>(方法參數列表)

如:

public voidShow(T  a){}

此泛型方法的使用時要給T指定一個實際的數據類型,如:

Show("hello");

其中方法的泛型類型列表中定義的泛型類型可以出現在方法的任何位置,包括返回值、參數、方法內,當然也可以不出現,比如下面這些都是合法的:

public TGet(T a) {return default(T) ;}

public intGet

public TGet(int a)  {return default(T);}

這上面用了default關鍵字,這個關鍵字可以取當前類型的默認初始值,這個關鍵字對於引用類型會返回null,對於數值類型會返回零。

另外,類中也可以出現泛型的重載方法,如:

voidDoWork(){}

voidDoWork(){}

voidDoWork<T,U>(){}

由於方法是在類中,所以泛型方法中的數據類型又三種情況,一種是類的泛型類型,一種是泛型方法自身的泛型類型,另外還可以是系統數據類型。泛型方法和非泛型方法或屬性、索引器可以互相調用。如:

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

}

}

類的泛型類型只能用於本類,方法的泛型類型只能用於本方法。不管誰定義的泛型,一旦定義了泛型類型,你可以就當泛型類型是一個真實的類型來用了。
6 典型的泛型類
.Net框架類庫中,System.Collections.Generic和System.Collections.ObjectModel命名空間中,分別定義了大量的泛型類和泛型接口,這些泛型類多爲集合類,因爲泛型最大的應用正體現於再集合中對於不同類型對象的管理。
下表列出了,.Net框架中常用的泛型類和泛型接口:
泛型類 說明
List 對應於ArrayList集合類,可以動態調整集合容量,通過索引方式訪問對象,支持排序、搜索和其他常見操作。
SortedList

發佈了21 篇原創文章 · 獲贊 6 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章