C#泛型秘诀(3)
4.5 使用相应的泛型版本替换Stack和Queue
问题
您希望通过将所有Stack和Queue对象替换为相应的泛型版本以提高应用程序的效率,并使得代码更易于使用。当结构体或其他值类型存储在这些数据结构中时,会导致装箱/拆箱操作,这时就需要这么做。
解决方案
使用System.Collections.Generic.Stack和System.Collections.Generic.Queue对象来替换现有的System.Collections.Stack和System.Collections.Queue对象。
这里有一个简单地使用System.Collections.Queue对象的简单例子:
{
// 创建一个非泛型队列对象
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对象
{
// 创建一个泛型队列对象.
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对象的例子
{
// 创建一个非泛型栈.
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对象
{
// 创建一个泛型栈对象.
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类型:
numericQueue.Enqueue(s); // 成功,因为进行了隐式转换
但如果一个类型不能被隐式转换为整数,如double,将会导致一个编译期错误。
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对象来说,这非常容易实现,代码如下:
{
// 创建一个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对象,其代码如下:
{
// 创建一个泛型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>类型中所有元素的一份拷贝。
阅读参考
查看MSDN文档中的“System.Collections.Stack Class”, “System.Collections.Generic.Stack Class”, “System.Collections.Queue Class”和“System.Collections.Generic.Queue Class”主题。