前面四章中,我们仅仅涉及到处理object类型的。如果你想要获取一个对象的特点类型,你需要去把object类型转化为实际的类型。在第四课中你可能会用一些特别的集合处理一些类型比如字符串型。但是加入几个特别的集合并不能解决类型安全和集合的问题。泛型集合就是来解决这些问题的:
这节课我们将会学到:
■ 创建和使用类型安全的列表Create and work with type-safe lists
■ 创建和使用类型安全的Queues
■ 创建和使用类型安全的stacks
■ 创建和使用类型安全的dictionaries
■ 创建和使用类型安全的链接列表集合
泛型如何工作:
程序是用来解决问题的。有时候我们去解决一个特别问题的需要,这种需要是一个非常普遍的问题。如,去收集一个有序的列表是一个非常普遍的问题。在.NET Framework中,ArrayList试图去解决这个问题,因为ArrayList并不知道哪种对象会被存储,它就简单地以object类型进行存储。.NET中任何类型能以一个object表示;因此,ArrayList能存储任何类型。问题解决了吗?
虽然集合以object作为存储方式解决了这个问题,它还是介绍了新的。如,如果你想要去存储整形到集合中,你就要写下边的代码:
myInts.Add( 1 );
myInts.Add( 2 );
myInts.Add( 3 );
foreach (Object i in myInts)
{
int number = (int)i;
}
好的,你创建了一个集合并在集合里加入了整形。你能够把从集合返回的Object类型转化为整形。但是当你加入如下代码时:
myInts.Add("4");
编译通过,但当你通过foreach 遍历它时,会抛出一个异常因为4是一个字符串而不是整形。这是个麻烦的事情。如果你的集合仅仅能处理整形这样会比较好。 你能写一个新的类去实现这个功能,如下:
public class IntList : ICollection, IEnumerable
{
private ArrayList _innerList = new ArrayList();
public void Add(int number)
{
_innerList.Add(number);
}
public int this[int index]
{
get
{
return (int)_innerList[index];
}
}
#region ICollection Members
// NOTE: ICollection Members are not shown here for brevity.
// You will need to implement ICollection on your own collections
#endregion
#region IEnumerable Members
// NOTE: IEnumerable Members are not shown here for brevity.
// You will need to implement IEnumerable on your own collections
#endregion
}
简而言之,你创建了一个集合支持基本的集合接口(ICollection 和 IEnumerable)。你使用一个ArrayList去收集集合项。结果,你制造了Add方法和一个索引器,并且它们接受整形类型。如下:
IntList myIntegers = new IntList();
myIntegers.Add(1);
myIntegers.Add(2);
myIntegers.Add(3);
// myIntegers.Add("4"); does not compile!
foreach (Object i in myIntegers)
{
int number = (int)i; // Never crashes
}
如果某人试图加入一个非整形的变量到类中,你会得到一个编译器的错误。foreach 代码不会出问题,因为不会让整形进入集合。问题解决了?难道我们写集合时要定义不同的具体的类型吗?幸运的是,我们能用泛型。不用去创建一个对一个具体的类型的集合,让我们写一个能对应任何类型的集合:
public class MyList<T> : ICollection, IEnumerable
{
private ArrayList _innerList = new ArrayList();
public void Add(T val)
{
_innerList.Add(val);
}
public T this[int index]
{
get
{
return (T)_innerList[index];
}
}
#region ICollection Members
// ...
#endregion
#region IEnumerable Members
// ...
#endregion
}
这个类与前面定义的集合没什么两样,只是没有用整形,而用了一个泛型变量
T.在用整形的每个地方我们都把它换为变量T. T在编译的是后会被具体的类型所替换。所以我们能用这个类去创造集合,而集合满足所有有效的.NET 类型, 如下:
MyList<int> myIntList = new MyList<int>();
myIntList.Add(1);
// myIntList.Add("4"); does not compile!
MyList<String> myStringList = new MyList<String>();
myStringList.Add("1");
// myStringList.Add(2); // does not compile!
当你使用这个泛型类的时候,你只要简单地用一个类型替换泛型变量就可以了。第一个例子创建了整形集合,但是这个泛型类也可以创建一个字符串集合或任何.NET类型的集合,甚至你自己定义的类型。在.NET Framework 中,泛型都有应用,但是我们看到的泛型通常是泛型集合类。注意我们不需要创建你自己的泛型列表集合---在framework 中已经有了很多这样的泛型集合。
提升安全性和性能
在.NET Framework中,泛型存在于大部分的类中。还有,几个新的集合只能用泛型变量。表4-20列出了类与泛型类的对应关系:
表4-20
Type Generic Type
ArrayList List<>
Queue Queue<>
Stack Stack<>
Hashtable Dictionary<>
SortedList SortedList<>
ListDictionary Dictionary<>
HybridDictionary Dictionary<>
OrderedDictionary Dictionary<>
SortedDictionary SortedDictionary<>
NameValueCollection Dictionary<>
DictionaryEntry NameValuePair<>
StringCollection List<String>
StringDictionary Dictionary<String>
N/A LinkedList<>
大部分的集合类都有与之相对应的泛型集合类。
泛型List类
泛型列表类用于创建简单的类型安全的有序的对象列表。如:如果想要得到一个整形List,你要为泛型参数创建一个整形类型。一旦你创建了一个泛型List类实例,你就能完成以下的行为:
■ 你能使用Add 方法加集合项到List 中,但是集合项的类型要匹配泛型参数的类型to add
■ 你也能使用索引器去获取List 中的集合项。
■ 你也能使用foreach 去迭代List 中的集合项。
以下是在List 中存储整形:
List<int> intList = new List<int>();
intList.Add(1);
intList.Add(2);
intList.Add(3);
int number = intList[0];
foreach (int i in intList)
{
Console.WriteLine(i);
}
泛型List类的使用如同ArrayList的使用一样的简单,只是增加了基于泛型参数类型的类型安全。像我们在第一课中看到的一样,我们能够通过调用Sort方法去为一个List排序一样。这个功能在泛型 List类中也是一样的,但是泛型List类中有一个新的overload的方法需要注意。这个Sort方法支持一个泛型delegate。什么是泛型delegate?它像泛型类或结构一样,但是泛型参数仅仅用于去定义delegate的约定。如:
泛型List类的Sort方法有一个泛型的Comparison delegate,用来做比较的泛型delegate定义如下:
public delegate int Comparison<T> (
T x,
T y
)
假定你想要反序的为一个List排序。你可以写一个完整的Comparer类去完成它。或者你能够简单的写一个方法去匹配用来做比较的泛型delegate就可以了:
static int ReverseIntComparison(int x, int y)
{
return y - x;
}
注意这个方法本身不是泛型,只要匹配用来做比较的泛型delegate就可以了。
(你的List是由整形组成的,所以你的比较必须也要保证两个参数都为整形) 这个一致性允许你调用排序方法。
intList.Sort(ReverseIntComparison);
这种方法比为一个很少用的比较去写一个完整的比较对象要容易的多。
泛型Queue 和 Stack类
这两个泛型类是Queue 和 Stack类的类型安全版本。为了使用一个泛型的Queue类型,你能创建一个Queue类的实例,如下:
■ 你能使用Enqueue去向Queue中加集合项,但是集合项的类型必须匹配Queue中泛型参数的具体类型。
■ 你能使用Dequeue去从Queue中抽取集合项. 如下:
Queue<String> que = new Queue<String>();
que.Enqueue("Hello");
String queued = que.Dequeue();
泛型Stack 也相同。如下:
■ 你能使用Push方法加集合项到Stack 中,但是集合项的类型必须匹配Stack中泛型参数的具体类型。
■ 你能使用Pop方法从Stack中获取集合项。如下:
Stack<int> serials = new Stack<int>();
serials.Push(1);
int serialNumber = serials.Pop();
泛型字典类
泛型字典类与Hashtable, ListDictionary, 和HybridDictionary类相似. 泛型字典类不像泛型的List,Stack 和Queue 类,它是用于在集合中存储键/值对。为了实现它,我们创建一个需要两个泛型参数的字典类。为了使用一个泛型字典类我们要定义两个泛型参数,如下:
1. 创建一个泛型字典类的实例,同时定义键和值的具体类型。
2. 使用索引器去从字典中添加和获取集合项,但这些集合项必须匹配字典中泛型参数的具体类型。
如下:
Dictionary<int, string> dict = new Dictionary<int, string>();
dict[3] = "Three";
dict[4] = "Four";
dict[1] = "One";
dict[2] = "Two";
String str = dict[3];
这个例子显示了使用一个整形做字典的键,同时使用字符串做字典的内容。
泛型字典和非泛型字典的一个重要的不同是,泛型字典不用一个结构DictionaryEntry对象而用泛型结构KeyValuePair。所以当你要获取单个的集合项还是遍历集合,你将需要使用一个称为KeyValuePair的泛型类。
泛型结构像泛型字典类一样有两个类型。一开始,你不要创建类型的实例;而是要从泛型字典类返回它们。例如:如果你要迭代一个Dictionary对象,枚举器返回一个KeyValuePair,KeyValuePair内有Dictionary类型定义了的键和值的类型。你能够在一个泛型Dictionary类中迭代集合项,如下:
1. 创建foreach 结构, 定义一个泛型KeyValuePair 结构的对象类型,每个迭代返回KeyValuePair结构。定义在KeyValuePair 的类型必须要匹配Dictionary 中定义的类型。
2. 在foreach 块内部,你能使用KeyValuePair ,通过Key和Value去获取键和值,如下:
foreach (KeyValuePair<int, string> i in dict)
{
Console.WriteLine("{0} = {1}", i.Key, i.Value);
}
泛型字典类保持列表中集合项的顺序。
泛型SortedList 和SortedDictionary 类
泛型SortedList 和SortedDictionary 类与泛型Dictionary 类似,但它保持着集合key 的有序排列。如下:
1. 创建有一个SortedList 实例,定义键和值的泛型参数。
2. 你能使用索引器去从SortedList中添加和获取集合项,但是这些集合项要匹配SortedList 中泛型的具体类型。
3. 创建foreach 结构, 定义一个泛型KeyValuePair 结构的对象类型,每个迭代返回KeyValuePair结构。定义在KeyValuePair 的类型必须要匹配SortedList中定义的类型。
4. 在foreach 块内部,你能使用KeyValuePair ,通过Key和Value去获取键和值,如下:
SortedList<string, int> sortList = new SortedList<string, int>();
sortList["One"] = 1;
sortList["Two"] = 2;
sortList["Three"] = 3;
foreach (KeyValuePair<string, int> i in sortList)
{
Console.WriteLine(i);
}
SortedDictionary 的使用也是一样的。如下:
1. 创建有一个SortedDictionary实例,定义键和值的泛型参数。
2. 你能使用索引器去从SortedDictionary中添加和获取集合项,但是这些集合项要匹配SortedDictionary中泛型的具体类型。
3. 创建foreach 结构, 定义一个泛型KeyValuePair 结构的对象类型,每个迭代返回KeyValuePair结构。定义在KeyValuePair 的类型必须要匹配SortedDictionary中定义的类型。
4. 在foreach 块内部,你能使用KeyValuePair ,通过Key和Value去获取键和值,如下:
SortedDictionary<string, int> sortedDict =
new SortedDictionary<string, int>();
sortedDict["One"] = 1;
sortedDict["Two"] = 2;
sortedDict["Three"] = 3;
foreach (KeyValuePair<string, int> i in sortedDict)
{
Console.WriteLine(i);
}
泛型LinkedList 类
泛型LinkedList 类是.NET中的一个新的集合类型,一个链接列表是一系列相互链接的集合项的集合。从任意的一个集合项,你不用访问集合本身就能找到下一项或前一项。
这是非常有用的,特别是在想去根据一个集合项去访问其它的集合项的时候。
表4-21和表4-22 显示了泛型类LinkedList 实现的接口:
表 4-21 LinkedList 属性
Name Description
Count 得到LinkedList 的节点个数。
First 得到LinkedList 的第一个节点。
Last 得到LinkedList 的最后一个节点。
表 4-22 LinkedList 方法
Name Description
AddAfter 在LinkedList中某个节点后加入新节点。
AddBefore 在LinkedList中某个节点前加入新节点。
AddFirst 在LinkedList中加入头节点。
AddLast 在LinkedList中加入尾节点。
Clear 清理在LinkedList中的所有节点。
Contains 测试一个值是不是包含在LinkedList 中。
CopyTo 把整个LinkedList 的集合项拷入Array 中。
Find 寻找第一个满足特定值的节点。
FindLast 寻找最后一个满足特定值的节点。
Remove 删除第一个满足特定值的节点
RemoveFirst 删除第一个满足特定值的节点。
RemoveLast 删除最后一个满足特定值的节点。
一个 LinkedList 包含一个LinkedListNode 对象的集合。当使用一个LinkedList ,你将主要的需求是得到和遍历这些节点。泛型类LinkedListNode 的属性如下表4-23所示:
表 4-23 LinkedListNode 属性
Name Description
List 得到这个节点所属的LinkedList。
Next 得到下一个节点。
Previous 得到上一个节点。
Value 得到这个节点的值。
对于泛型类LinkedList 的一个特别点是:它实现了枚举器(ILinkedListEnumerator) ,它允许不使用LinkedListNode 对象去枚举列表值。这个行为不像泛型字典类型,泛型字典类型的枚举器返回一个泛型NameValuePair对象。因为LinkedListNode对象能够被用于遍历列表所以区别是存在的,但是在每个节点里只有一个数据项。因此,没有必要在遍历工程中返回节点。为了使用一个LinkedList, 你能创建一个LinkedList类的实例,定义存储在列表中的值的类型,然后你能执行以下的行为:
■ 你能使用AddFirst 和 AddLast 方法去加集合项到列表的头和尾。
■ 你也能使用AddBefore 和 AddAfter 方法把值加入到列表中间。为了使用这些方法,你需要访问想要插入的值的前一个位置或后一个位置。
你也能使用foreach结构去遍历LinkedList的值。注意你遍历的类型是值,而不是节点。如下在LinkedList中存储了字符串:
LinkedList<String> links = new LinkedList<string>();
LinkedListNode<string> first = links.AddLast("First");
LinkedListNode<string> last = links.AddFirst("Last");
LinkedListNode<string> second = links.AddBefore(last, "Second");
links.AddAfter(second, "Third");
foreach (string s in links)
{
Console.WriteLine(s);
}
泛型集合类的结构:
像非泛型集合一样,泛型集合的很多部分的工作方式是相同的。这些共性通过泛型集合接口,泛型枚举器和泛型比较去实现。
泛型集合接口:
在非泛型集合中,一系列的接口定义了集合的一些一致性的功能。这些接口包括IEnumerable, ICollection, IList,等等。虽然泛型集合实现了这些接口,它们也提供了这些了与这些接口相同的泛型版本,如:
List<String> stringList = new List<String>();
// ...
IList theList = (IList)stringList;
object firstItem = theList[0];
这个非泛型的IList接口被泛型List集合实现。但是还有一个泛型IList接口能够提供一个类型安全的方式去获取数据,如:
IList<String> typeSafeList = (IList<String>) stringList;
String firstString = typeSafeList[0];
对于ICollection, IDictionary, IEnumerable 是相同的,总的来说,如果你正操作泛型集合类但是想去用一个接口代替具体的类,你应当使用接口的泛型版本去保证其类型安全。
泛型集合的枚举器:
这课中提到的泛型集合都支持集合内的值的迭代。为了实现迭代,每个集合支持自己的泛型枚举器结构。这个枚举器结构被定义到相同类型的父类。如果你需要使用枚举器而不使用foreach,你能够通过调用GetEnumerator 方法去获得枚举器,如下:
List<string> stringList = new List<string>();
// ...
List<string>.Enumerator e = stringList.GetEnumerator();
while (e.MoveNext())
{
// Typesafe Access to the current item
string s = e.Current;
}
通过使用Enumerator 结构,你能以一种类型安全的方式从泛型结构中得到当前的集合项。所以的泛型集合都支持Enumerator 结构。
泛型比较
在前面几课中,我们看到了我们能使用IComparer 和 IEqualityComparer接口,为我们的集合提供排序和比较的行为。对于泛型集合,有这些接口的泛型版本。当我们需要写我们对接口IComparer和IEqualityComparer的实现的时候,泛型基础类能为我们多许多的工作。这些类是泛型Comparer类和泛型EqualityComparer类。如果你需要实现你自己的比较逻辑,你应当使用这些基础类,实现任何抽象方法和重写任何默认的你需要的行为,如:
class MyComparer<T> : Comparer<T>
{
public override int Compare(T x, T y)
{
return x.GetHashCode() - y.GetHashCode();
}
}
写你自己的集合:
在这章涉及的集合接口(如:IList和ICollection)能被用于去实现你自己的集合。你能实现这几个接口以写出你自己的集合,然后.NET Framework 将会把你的类当作集合看待。对于很多集合,有许多的工作都是相同的。.NET Framework 有几个基础类去封装这些公共的行为:
■ CollectionBase
■ ReadOnlyCollectionBase
■ DictionaryBase
这些基础类能够成为你自己的集合的基础。CollectionBase 类支持IList, Ienumerable, 和 ICollection 接口.从CollectionBase继承将使你拥有一个已经实现了这些接口的集合。你使用这个CollectionBase类当你需要一个简单的有具体功能的集合,你不必使用内建的集合。像CollectionBase类,ReadOnlyCollectionBase支持IList, Ienumerable, 和 ICollection 接口。不同的是,在ReadOnlyCollectionBase中,不支持从集合类外改变集合类。这样的设计是合理的,因为你需要把这个集合类设为只读的。不像CollectionBase 和 ReadOnlyCollection基础类,DictionaryBase实现了IDictionary, IEnumerable, 和 ICollection接口。当你需要实现你自己的有键的集合时,DictionaryBase基础类是有用的。在.NET 2.0之前,为了建造类型安全的集合,使用这些接口去创建你自己的集合是很普遍的。现在泛型可以利用了,如果你的要求仅仅是一个类型安全的集合使用泛型集合是个更好的选择。