4.5 使用相應的泛型版本替換Stack和Queue
問題
您希望通過將所有Stack和Queue對象替換為相應的泛型版本以提高應用程序的效率,並使得代碼更易於使用。當結構體或其他值類型存儲在這些數據結構中時,會導致裝箱/拆箱操作,這時就需要這麼做。
解決方案
使用System.Collections.Generic.Stack和System.Collections.Generic.Queue對象來替換現有的System.Collections.Stack和System.Collections.Queue對象。
這裡有一個簡單地使用System.Collections.Queue對象的簡單例子:
public static void UseNonGenericQueue()
{
// 創建一個非泛型隊列對象
Queue numericQueue = new Queue();
// 進隊(導致裝箱操作).
numericQueue.Enqueue(1);
numericQueue.Enqueue(2);
numericQueue.Enqueue(3);
//出隊並顯示項(導致拆箱操作)
Console.WriteLine(numericQueue.Dequeue());
Console.WriteLine(numericQueue.Dequeue());
Console.WriteLine(numericQueue.Dequeue().ToString());
}
下面是相同的代碼使用了System.Collections.Generic.Queue對象
public static void UseGenericQueue()
{
// 創建一個泛型隊列對象.
Queue<int> numericQueue = new Queue<int>();
// 進隊.
numericQueue.Enqueue(1);
numericQueue.Enqueue(2);
numericQueue.Enqueue(3);
// 出隊並顯示項目.
Console.WriteLine(numericQueue.Dequeue());
Console.WriteLine(numericQueue.Dequeue());
Console.WriteLine(numericQueue.Dequeue());
}
下面是一個簡單地使用System.Collections.Stack對象的例子
public static void UseNonGenericStack()
{
// 創建一個非泛型棧.
Stack numericStack = new Stack();
// 進棧(導致裝箱操作).
numericStack.Push(1);
numericStack.Push(2);
numericStack.Push(3);
// 出棧並顯示項目(導致拆箱操作).
Console.WriteLine(numericStack.Pop().ToString());
Console.WriteLine(numericStack.Pop().ToString());
Console.WriteLine(numericStack.Pop().ToString());
}
下面是相同的代碼使用了System.Collections.Generic.Stack對象
public static void UseGenericStack()
{
// 創建一個泛型棧對象.
Stack<int> numericStack = new Stack<int>();
// 進棧.
numericStack.Push(1);
numericStack.Push(2);
numericStack.Push(3);
// 出棧並顯示項目.
Console.WriteLine(numericStack.Pop().ToString());
Console.WriteLine(numericStack.Pop().ToString());
Console.WriteLine(numericStack.Pop().ToString());
}
討論
表面上,泛型和非泛型的Queue和Stack類非常相象。但在內部機制上卻有極大地不同。除了實例化對象之外,泛型版的Queue和Stack和非泛型版的Queue和Stack的使用是基本相同的。泛型結構為了創建一個類型需要一個類型參數。在此例中,類型參數是int。這個類型參數表明Queue和Stack對象將只能包含整數類型和能夠被隱式轉換為整數的類型,比如short類型:
short s = 300;
numericQueue.Enqueue(s); // 成功,因為進行了隱式轉換
但如果一個類型不能被隱式轉換為整數,如double,將會導致一個編譯期錯誤。
double d = 300;
numericQueue.Enqueue(d); // 錯誤,不能進行隱式轉換
numericQueue.Enqueue((int)d); // 成功,因為進行了顯式轉換
非泛型結構不需要類型參數,因為非泛型版的Queue和Stack對象只能包含Object類型。(譯者注:任何對象都可以隱式轉換為Object,並於這一點有不清楚的請參考:
http://cgbluesky.blog.163.com/blog/static/24123558200712493419458/ )
當需要在Queue或Stack的泛型版和非泛型版做出選擇時,您需要決定使用強類型Queue或Stack對象(也就是泛型版Queue或Stack類)還是弱類型的Queue或Stack對象(也就是非泛型版Queue或Stack類)。選擇泛型版Queue或Stack類相對於非泛型版來說會提供很多好處,包括:
類型安全
包含在數據結構中的每個元素都是指定的類型。這意味著當在數據結構中進行添加或刪除操作時不再需要將它們轉換成object類型。您不能在一個單一數據結構中存儲多種不同的類型;您總是知道數據結構中存儲的是什麼類型。在編譯期進行類型檢查優於在運行期進行檢查。可以歸結為更簡潔的代碼,更好的性能,更少的錯誤。
縮短開發周期
創建一個類型安全的數據結構而不使用泛型意味著不得不從System.Collections.Stack或System.Collections.Queue繼承創建自己的子類,這是一個耗時並容易發生錯誤的工作。而泛型只需您在編譯期簡單地告訴Queue或Stack對象控制什麼類型。
性能
使用泛型Queue或Stack在添加和刪除元素時避免了潛在的費時的類型轉換的發生。另外在把值類型添加進Queue或Stack時不會發生裝箱操作,從Queue或Stack刪除值類型時也不會發生拆箱操作。
容易閱讀的代碼
您的基礎代碼將變得非常少,因為不再需要從非泛型版的Queue或Stack類繼續創建您自己的強類型類。另外泛型代碼的類型安全功能將使在代碼中使用Queue或Stack類的目的變得更容易理解。
泛型版和非泛型版的Queue和Stack的不同之處在於兩種類之間的成員實現。在非泛型版實現而在泛型版沒有實現的成員列表如下:
Clone 方法
IsSynchronized 屬性
SyncRoot 屬性
Synchronized 方法
非泛型版Queue和Stack類中存在Clone方法是因為只有它實現了Icolneable接口。但非泛型版Queue和Stack類實現的其他接口是一樣的。
一個彌補泛型版Queue和Stack類中不存在Clone方法的途徑是接收一個Ienumerable<T>類型。因為這是Queue和Stack類實現的接口之一,對於Queue對象來說,這非常容易實現,代碼如下:
public static void CloneQueue()
{
// 創建一個Queue對象.
Queue<int> numericQueue = new Queue<int>();
// 進隊
numericQueue.Enqueue(1);
numericQueue.Enqueue(2);
numericQueue.Enqueue(3);
// 創建一個克隆的numericQueue.
Queue<int> clonedNumericQueue = new Queue<int>(numericQueue);
// 這只是簡單地看一下裡面的值,並非出隊
foreach (int i in clonedNumericQueue)
{
Console.WriteLine("foreach: " + i.ToString());
}
// 出隊並顯示項目
Console.WriteLine(clonedNumericQueue.Dequeue().ToString());
Console.WriteLine(clonedNumericQueue.Dequeue().ToString());
Console.WriteLine(clonedNumericQueue.Dequeue().ToString());
}
方法的輸出結果如下:
foreach: 1
foreach: 2
foreach: 3
1
2
3
對於Stack對象,其代碼如下:
public static void CloneStack()
{
// 創建一個泛型Stack對象
Stack<int> numericStack = new Stack<int>();
// 進棧
numericStack.Push(1);
numericStack.Push(2);
numericStack.Push(3);
// 克隆numericStack 對象.
Stack<int> clonedNumericStack = new Stack<int>(numericStack);
// 這只是簡單地看一下裡面的值,並非出棧
foreach (int i in clonedNumericStack)
{
Console.WriteLine("foreach: " + i.ToString());
}
// 出棧並顯示項目
Console.WriteLine(clonedNumericStack.Pop().ToString());
Console.WriteLine(clonedNumericStack.Pop().ToString());
Console.WriteLine(clonedNumericStack.Pop().ToString());
}
方法的輸出結果如下:
foreach: 1
foreach: 2
foreach: 3
1
2
3
構造方法創建了一個新的Queue或Stack實例,並包含了Ienumerable<T>類型中所有元素的一份拷貝。