最近工作不是很忙,抽出時間來看看C#中基礎的東西,也算是“溫故而知新”了,於是就看到了泛型這塊兒,看了園子裡其他園友的文章,講的都很到位。這篇文章本著簡單、容易理解為前提,記錄下我自己對泛型的認識,並記錄下來,方便以後查看。
泛型是一種開放式類型,它的出現保證了我們可以創建類型安全的集合。
<1>當一種邏輯適用於多種數據類型時,可以采用泛型,簡化代碼,提高代碼的復用性。
<2>避免不必要的裝箱(Boxing)和拆箱(Unboxing)操作。
<3>避免不必要的類型強制轉換。
下面通過一個自定義的棧,來對上面的應用場景進行解釋。
首先定義一個棧,該棧有一個含參構造函數,有三個方法,元素的入棧Push、元素的出棧Pop、元素的輸出Out。代碼如下:
/// <summary> /// 自定義棧 /// </summary> /// <typeparam name="?"></typeparam> class Stack { private int[] arr; private int length = 0; private int topElement; /// <summary> /// 設置或獲取棧頂元素 /// </summary> public int TopElement { get { this.topElement= arr[length-1]; return this.topElement; } set { this.topElement = value; } } /// <summary> /// 棧中已存在元素數量 /// </summary> public int Length { get { return this.length; } } /// <summary> /// 構造函數 /// </summary> /// <param name="m"></param> public Stack(int m) { this.arr = new int[m]; } /// <summary> /// 將棧頂元素出棧 /// </summary> /// <returns></returns> public int Pop() { if (this.length <= 0) return -1; else { int popElement = this.topElement; arr[this.length-1] = 0; this.length = this.length - 1; return popElement; } } /// <summary> /// 將元素入棧 /// </summary> /// <param name="item">目標元素</param> public void Push(int item) { if (length >= this.arr.Length) return; this.arr[length] = item; this.topElement = this.arr[length]; this.length = this.length + 1; } /// <summary> /// 輸出棧內所有元素 /// </summary> /// <param name="stack">目標棧</param> public void Out(Stack stack) { if (stack.arr.Length <= 0) return; foreach (int item in arr) { Console.WriteLine(item); } Console.ReadLine(); } }
以上代碼可以正常運行,實現了一個棧的基本功能,但是我想把這個邏輯放在string類型上也通用,怎麼辦?有人說那好辦,把int替換成string就好咯,代碼如下:
/// <summary> /// 自定義棧 /// </summary> /// <typeparam name="?"></typeparam> class StringStack { private string[] arr; private int length = 0; private string topElement; /// <summary> /// 設置或獲取棧頂元素 /// </summary> public string TopElement { get { this.topElement = arr[length - 1]; return this.topElement; } set { this.topElement = value; } } /// <summary> /// 棧中已存在元素數量 /// </summary> public int Length { get { return this.length; } } /// <summary> /// 構造函數 /// </summary> /// <param name="m"></param> public StringStack(int m) { this.arr = new string[m]; } /// <summary> /// 將棧頂元素出棧 /// </summary> /// <returns></returns> public string Pop() { if (this.length <= 0) return "Empty"; else { string popElement = this.topElement; arr[this.length - 1] = string.Empty; this.length = this.length - 1; return popElement; } } /// <summary> /// 將元素入棧 /// </summary> /// <param name="item">目標元素</param> public void Push(string item) { if (length >= this.arr.Length) return; this.arr[length] = item; this.topElement = this.arr[length]; this.length = this.length + 1; } /// <summary> /// 輸出棧內所有元素 /// </summary> /// <param name="stack">目標棧</param> public void Out(StringStack stack) { if (stack.arr.Length <= 0) return; foreach (string item in arr) { Console.WriteLine(item); } Console.ReadLine(); } }
經過測試發現,上面的棧也可以正常工作了,但是問題又來了,我想把這個棧的邏輯再應用到long類型上怎麼辦?聰明的人不會在同一個問題上絆倒三次,既然有那麼多類型需要用到這個棧的邏輯,那我索性就給他來個Object,這下總可以了吧~於是Object類型的棧橫空出世,代碼如下:
/// <summary> /// 自定義棧 /// </summary> /// <typeparam name="?"></typeparam> class ObjectStack { private object[] arr; private int length = 0; private object topElement; /// <summary> /// 設置或獲取棧頂元素 /// </summary> public object TopElement { get { this.topElement = arr[length - 1]; return this.topElement; } set { this.topElement = value; } } /// <summary> /// 棧中已存在元素數量 /// </summary> public int Length { get { return this.length; } } /// <summary> /// 構造函數 /// </summary> /// <param name="m"></param> public ObjectStack(int m) { this.arr = new string[m]; } /// <summary> /// 將棧頂元素出棧 /// </summary> /// <returns></returns> public object Pop() { if (this.length <= 0) return null; else { object popElement = this.topElement; arr[this.length - 1] = null; this.length = this.length - 1; return popElement; } } /// <summary> /// 將元素入棧 /// </summary> /// <param name="item">目標元素</param> public void Push(object item) { if (length >= this.arr.Length) return; this.arr[length] = item; this.topElement = this.arr[length]; this.length = this.length + 1; } /// <summary> /// 輸出棧內所有元素 /// </summary> /// <param name="stack">目標棧</param> public void Out(ObjectStack stack) { if (stack.arr.Length <= 0) return; foreach (object item in arr) { Console.WriteLine(item); } Console.ReadLine(); } }
經過重構之後,這個棧邏輯可以適用於任何類型,這是毫無疑問的,但是還有一個問題,來看調用的代碼:
static void Main(string[] args) { ObjectStack ostd = new ObjectStack(3); ostd.Push(true); ostd.Push("http://www.cnblogs.com/xhb-bky-blog/"); ostd.Push(12); int a = (int)ostd.Pop(); string b = ostd.Pop().ToString(); bool c = (bool)ostd.Pop(); Console.ReadLine(); }
在調用代碼中,我們居然看到了裝箱和拆箱操作,而裝箱操作和拆箱操作是要額外耗費cpu和內存資源的。再來看下面一段代碼:
static void Main(string[] args) { ObjectStack ostd = new ObjectStack(3); Good good1 = new Good("pencil", 5); Good good2 = new Good("eraser", 2); Good good3 = new Good("pencilbox", 15); ostd.Push(good1); ostd.Push(good2); ostd.Push(good3); Good mygood = (Good)ostd.Pop();//注意 Console.ReadLine(); }
上面的代碼中發生了類型強制轉換,這表明該段代碼是不安全的,雖然可以通過編譯,但是如果類型對應不上,就會把錯誤推遲到運行期。為了解決上面的三個問題,泛型應運而生,來看一下泛型是如何解決這些問題的,看代碼:
/// <summary> /// 自定義棧 /// </summary> /// <typeparam name="T"></typeparam> class Stack<T> where T:IComparable { private T[] arr; private int length = 0; private T topElement; /// <summary> /// 設置或獲取棧頂元素 /// </summary> public T TopElement { get { this.topElement = arr[length - 1]; return this.topElement; } set { this.topElement = value; } } /// <summary> /// 棧中已存在元素數量 /// </summary> public int Length { get { return this.length; } } /// <summary> /// 構造函數 /// </summary> /// <param name="m"></param> public Stack(int m) { this.arr = new T[m]; } /// <summary> /// 將棧頂元素出棧 /// </summary> /// <returns></returns> public T Pop() { //T t = new T();//必須聲明new if (this.length <= 0) return default(T); else { T popElement = this.topElement; arr[this.length - 1] = default(T); this.length = this.length - 1; return popElement; } } /// <summary> /// 將元素入棧 /// </summary> /// <param name="item">目標元素</param> public void Push(T item) { if (length >= this.arr.Length) return; this.arr[length] = item; this.topElement = this.arr[length]; this.length = this.length + 1; } /// <summary> /// 輸出棧內所有元素 /// </summary> /// <param name="stack">目標棧</param> public void Out(Stack<T> stack) { if (stack.arr.Length <= 0) return; foreach (T item in arr) { Console.WriteLine(item); } Console.ReadLine(); } }
在泛型中,類名後面的Stack<T>中的T表示它操作的是一個未指定的數據類型,也稱為開放式類型,因為T是一個不明確的類型,在實例化的時候才能知道T的實際類型是什麼,實例化之後也就變成了封閉式類型,如Stack<String>,因為Stack<String>已經明確了T是String類型的。另外一點,Stack<String>和Stack<T>沒有任何繼承上的關系,而是兩種完全獨立的類型。下面來看下泛型的神奇之處吧:
static void Main(string[] args) { Stack<int> std = new Stack<int>(2); std.Push(21); std.Push("Hello");//編譯不通過,保證了類型安全 int a = std.Pop();//未發生拆裝箱 Stack<Good> stdg = new Stack<Good>(2); Good sell = new Good("box", 10); stdg.Push(sell); Good buy = stdg.Pop();//未發生強制類型轉換 Console.ReadLine(); }
以上是泛型的簡單應用,實際應用中,泛型還有很多其他的特性,如泛型的約束、泛型的方法、接口、委托以及繼承等。要理解這些特性,需要把基礎打好,熟練掌握泛型的應用場景,才能事半功倍。