最近工作不是很忙,抽出時間來看看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();
}
以上是泛型的簡單應用,實際應用中,泛型還有很多其他的特性,如泛型的約束、泛型的方法、接口、委托以及繼承等。要理解這些特性,需要把基礎打好,熟練掌握泛型的應用場景,才能事半功倍。