System.Collections.Generic命名空间

实际上,本书前面的每个应用程序都有如下命名空间:

using System;

using System.Collections.Generic;

using System.Text;

System命名空间包含.NET应用程序使用的大多数基本类型。System.Text命名空间包含与字符串处理和编码相关的类型,但System.Collections.Generic命名空间包含什么类型?为什么要在默认情况下把它包含在控制台应用程序中?

这个命名空间包含用于处理集合的泛型类型,使用得非常频繁,用using语句配置它,使用起来就不必添加限定符了。

如本章前面所述,下面就介绍这些泛型类型,它们可以使工作更容易完成,可以毫不费力地创建强类型化的集合类。表12-2描述了本节要介绍的类型,本章后面还会详细阐述这些类型。

表 12-2

类 型

说 明

List<T>

T类型对象的集合

Dictionary<K, V>

V类型的项与K类型的键值相关的集合

 

后面还会介绍和这些类一起使用的各种接口和委托。

1. List<T>

使用这个泛型的集合类型会更快捷、更简单,而不是像上一章那样,从CollectionBase中派生一个类,实现需要的方法。它的另一个好处是正常情况下需要实现的许多方法(例如Add())已经自动实现了。

创建T类型对象的集合需要如下代码:

List<T> myCollection = new List<T>();

这就足够了。没有定义类、实现方法和进行其他操作。还可以把List<T>对象传送给构造函数,在集合中设置项的起始列表。

使用这个语法实例化的对象将支持表12-3中的方法和属性(其中,提供给List<T>泛型的类型是T)。

表 12-3

成 员

说 明

int Count

该属性给出集合中项的个数

void Add(T item)

把item添加到集合中

void AddRange(IEnumerable<T>)

把多个项添加到集合中

IList<T> AsReadOnly()

给集合返回一个只读接口

int Capacity

获取或设置集合可以包含的项数

void Clear()

删除集合中的所有项

bool Contains(T item)

确定item是否包含在集合中

void CopyTo(T[] array, int index)

把集合中的项复制到数组array中,从数组的索引index开始

IEnumerator<T> GetEnumerator()

获取一个IEnumerator<T>实例,用于迭代集合。注意返回的接口强类型化为T,所以在foreach循环中不需要类型转换

int IndexOf(T item)

获取item的索引,如果项没有包含在集合中,就返回-1

void Insert(int index, T item)

把item插入到集合的指定索引上

bool Remove(T item)

从集合中删除第一个item,并返回true;如果item不包含在集合中,就返回false

void RemoveAt(int index)

从集合中删除索引index处的项

 

List<T>还有一个Item属性,可以进行类似于数组的访问,如下所示:

T itemAtIndex2 = myCollectionOfT[2];

这个类还支持其他几个方法,但上述知识已足以开始使用该类了。

下面的示例介绍如何使用Collection<T>。

试试看:使用Collection<T>

(1) 在目录C:/BegVCSharp/Chapter12下创建一个新控制台应用程序项目Ch12Ex02。

(2) 在Solution Explorer窗口中右击项目名称,选择Add | Add Existing Item...选项。

(3) 在C:/BegVCSharp/Chapter11/Ch11Ex01/Ch11Ex01目录下选择Animal.cs、Cow.cs和Chicken.cs文件,单击Add。

(4) 修改这3个文件中的命名空间声明,如下所示:

namespace Ch12Ex02

(5) 修改Program.cs中的代码,如下所示:

static void Main(string[] args)

{

List<Animal> animalCollection = new List<Animal>();

animalCollection.Add(new Cow("Jack"));

animalCollection.Add(new Chicken("Vera"));

foreach (Animal myAnimal in animalCollection)

{

myAnimal.Feed();

}

Console.ReadKey();

}

(6) 执行应用程序,结果与上一章的Ch11Ex02相同。

示例的说明

这个示例与Ch11Ex02只有两个区别。第一个区别是下面的代码:

Animals animalCollection = new Animals();

被替换为:

List<Animal> animalCollection = new List<Animal>();

第二个区别比较重要:项目中不再有Animals集合类。前面为创建这个类所做的工作现在用一行代码即可完成,即使用泛型的集合类。

获得相同效果的另一个方法是不修改Program.cs中的代码,使用Animals的如下定义:

public class Animals : List<Animal>

{

}

这么做的优点是,能比较容易看懂Program.cs中的代码,还可以在合适时给Animals类添加额外的成员。

为什么不从CollectionBase中派生类?这是一个很好的问题。实际上,在许多情况下,我们都不会从CollectionBase中派生类。知道内部工作原理肯定是件好事,因为List<T>以相同的方式工作,但CollectionBase是向后兼容的。使用CollectionBase的惟一场合是要更多地控制向类的用户展示的成员。如果希望集合类的Add()方法使用内部访问修饰符,则使用CollectionBase是最佳选择。

注意:

也可以把要使用的初始容量(作为int)传递给List<T>的构造函数,或者传递使用IEnumerable<T>接口的初始项列表。支持这个接口的类包括List<T>。

2. 对泛型列表进行排序和搜索

给泛型列表进行排序与对其他列表进行排序是一样的。在上一章中,介绍了如何使用IComparer和IComparable接口比较两个对象,然后对该类型的对象列表排序。这里惟一的区别是,可以使用泛型接口IComparer<T>和IComparable<T>,它们略有区别、且针对特定类型的方法。表12-4列出了它们的区别。

表 12-4

泛 型 方 法

非泛型方法

区 别

int IComparable<T>.

CompareTo(T otherObj)

int IComparable.

CompareTo( object, otherObj)

泛型版本中是强类型化的

bool IComparable<T>.

Equals(T otherObj)

N/A

在非泛型接口中不存在,可以使用object.Equals()替代

int IComparer<T>.

Compare(T objectA, T objectB)

int IComparer.

Compare(object objectA, object objectB)

泛型版本中是强类型化的

bool IComparer<T>.

Equals(T objectA, T objectB)

N/A

在非泛型接口中不存在,可以使用object.Equals()替代

int IComparer<T>. GetHashCode (T objectA)

N/A

在非泛型接口中不存在,可以使用object. GetHashCode()替代

 

要对List<T>排序,可以在要排序的类型上提供IComparable<T>接口,或者提供IComparer<T>接口。另外,还可以提供泛型委托,作为排序方法。从了解工作原理的角度来看,这非常有趣,因为实现上述接口并不比实现其非泛型版本更麻烦。

一般情况下,给列表排序需要一个方法,来比较T类型的两个对象。要在列表中搜索,也需要一个方法来检查T类型的对象,看看它是否满足某个条件。定义这样的方法很简单,这里给出两个可以使用的泛型委托:

● Comparison<T>:这个委托类型用于排序方法,其签名是int method (T objectA, T objectB)。

● Predicate<T>:这个委托类型用于搜索方法,其签名是bool method (T targetObject)。

可以定义任意个这样的方法,使用它们实现List<T>的搜索和排序方法。下面的示例进行了演示。

试试看:List<T>的搜索和排序

(1) 在目录C:/BegVCSharp/Chapter12下创建一个新控制台应用程序项目Ch12Ex03。

(2) 在Solution Explorer窗口中右击项目名称,选择Add | Add Existing Item...选项。

(3) 在C:/BegVCSharp/Chapter12/Ch12Ex01/Ch12Ex01目录下选择Vector.cs文件,单击Add。

(4) 修改这个文件中的命名空间声明,如下所示:

namespace Ch12Ex03

(5) 添加一个新类Vectors。

(6) 修改Vectors.cs中的代码,如下所示:

 

 

public class Vectors : List<Vector>

{

public Vectors()

{

}

 

public Vectors(IEnumerable<Vector> initialItems)

{

foreach (Vector vector in initialItems)

{

Add(vector);

}

}

 

public string Sum()

{

StringBuilder sb = new StringBuilder();

Vector currentPoint = new Vector(0.0, 0.0);

sb.Append("origin");

foreach (Vector vector in this)

{

sb.AppendFormat(" + {0}", vector);

currentPoint += vector;

}

sb.AppendFormat(" = {0}", currentPoint);

return sb.ToString();

}

}

(7) 添加一个新类VectorDelegates。

(8) 修改VectorDelegates.cs中的代码,如下所示:

public static class VectorDelegates

{

public static int Compare(Vector x, Vector y)

{

if (x.R > y.R)

{

return 1;

}

else if (x.R < y.R)

{

return -1;

}

return 0;

}

 

public static bool TopRightQuadrant(Vector target)

{

if (target.Theta >= 0.0 && target.Theta <= 90.0)

{

return true;

}

else

{

return false;

}

}

}

(9) 修改Program.cs中的代码,如下所示:

static void Main(string[] args)

{

Vectors route = new Vectors();

route.Add(new Vector(2.0, 90.0));

route.Add(new Vector(1.0, 180.0));

route.Add(new Vector(0.5, 45.0));

route.Add(new Vector(2.5, 315.0));

 

Console.WriteLine(route.Sum());

 

Comparison<Vector> sorter = new Comparison<Vector>(VectorDelegates.Compare);

route.Sort(sorter);

Console.WriteLine(route.Sum());

 

Predicate<Vector> searcher =

new Predicate<Vector>(VectorDelegates.TopRightQuadrant);

Vectors topRightQuadrantRoute = new Vectors(route.FindAll(searcher));

Console.WriteLine(topRightQuadrantRoute.Sum());

 

Console.ReadKey();

}

(10) 执行应用程序,结果如图12-4所示。

图 12-4

示例的说明

在这个示例中,为Ch12Ex01中的Vector类创建了一个集合类Vectors。可以只使用List <Vector>类型的变量,但因为需要其他功能,所以使用了一个新类Vectors,它派生自List <Vector>,允许添加需要的其他成员。

该类有一个成员Sum(),依次返回每个矢量的字符串列表,并在最后把它们加在一起(使用源类Vector的重载+运算符)。每个矢量都可以看作“方向+距离”,所以这个矢量列表构成了一条有端点的路径。

public string Sum()

{

StringBuilder sb = new StringBuilder();

Vector currentPoint = new Vector(0.0, 0.0);

sb.Append("origin");

foreach (Vector vector in this)

{

sb.AppendFormat(" + {0}", vector);

currentPoint += vector;

}

sb.AppendFormat(" = {0}", currentPoint);

return sb.ToString();

}

这个方法使用System.Text命名空间中的StringBuilder类来构建响应字符串。这个类包含Append()和AppendFormat()等成员(这里使用),所以很容易构建字符串,其性能也高于连接各个字符串。使用这个类的ToString()方法即可获得最终的字符串。

本例还创建了两个用作委托的方法,作为VectorDelegates的静态成员。Compare()用于比较(排序),TopRightQuadrant()用于搜索。下面在讨论Program.cs中的代码时介绍它们。

Main()中的代码首先初始化Vectors集合,给它添加几个Vector对象:

Vectors route = new Vectors();

route.Add(new Vector(2.0, 90.0));

route.Add(new Vector(1.0, 180.0));

route.Add(new Vector(0.5, 45.0));

route.Add(new Vector(2.5, 315.0));

如前所述,Vectors.Sum()方法用于输出集合中的项,这次是按照其初始顺序输出:

Console.WriteLine(route.Sum());

接着,创建第一个委托sorter,这个委托是Comparison<Vector>类型的,因此可以赋予带如下签名的方法:

int method(Vector objectA, Vector objectB)

它匹配VectorDelegates.Compare(),该方法就是赋予委托的方法。

Comparison<Vector> sorter = new Comparison<Vector>(VectorDelegates.Compare);

Compare()比较两个矢量的大小,如下所示:

public static int Compare(Vector x, Vector y)

{

if (x.R > y.R)

{

return 1;

}

else if (x.R < y.R)

{

return -1;

}

return 0;

}

这样就可以按大小对矢量排序了:

route.Sort(sorter);

Console.WriteLine(route.Sum());

应用程序给出了我们期望的结果—— 汇总的结果是一样的,因为“矢量路径”的端点顺序与执行各个步骤的顺序相同。

然后,进行搜索,获取集合中的一个矢量子集。这需要使用VectorDelegates.TopRight Quadrant()来实现:

public static bool TopRightQuadrant(Vector target)

{

if (target.Theta >= 0.0 && target.Theta <= 90.0)

{

return true;

}

else

{

return false;

}

}

如果方法的Vector参数值是介于0到90°之间的Theta值,该方法就返回true,也就是说,它在前面的排序图中指向上或右。

在主函数体中,通过Predicate<Vector>类型的委托使用这个方法,如下所示:

Predicate<Vector> searcher =

new Predicate<Vector>(VectorDelegates.TopRightQuadrant);

Vectors topRightQuadrantRoute = new Vectors(route.FindAll(searcher));

Console.WriteLine(topRightQuadrantRoute.Sum());

这需要在Vectors中定义构造函数:

public Vectors(IEnumerable<Vector> initialItems)

{

foreach (Vector vector in initialItems)

{

Add(vector);

}

}

其中,使用IEnumerable<Vector>的实例初始化了一个新的Vectors集合,这是必须的,因为List<Vector>.FindAll()返回一个List<Vector>实例,而不是Vectors实例。

搜索的结果是,只返回Vector对象的一个子集,所以汇总的结果不同(这正是我们希望的)。

2. Dictionary<K, V>

这个类型可以定义键/值对的集合。与本章前面介绍的其他泛型集合类型不同,这个类需要实例化两个类型,分别用于键和值,以表示集合中的各个项。

 

实例化Dictionary<K, V>对象后,就可以对它执行与继承自DictionaryBase的类相同的一些操作,但要使用已有的类型安全的方法和属性。例如,可以使用强类型化的Add()方法添加键/值对。

Dictionary<string, int> things = new Dictionary<string, int>();

things.Add("Green Things", 29);

things.Add("Blue Things", 94);

things.Add("Yellow Things", 34);

things.Add("Red Things", 52);

things.Add("Brown Things", 27);

可以使用Keys和Values属性迭代集合中的键和值:

foreach (string key in things.Keys)

{

Console.WriteLine(key);

}

 

foreach (int value in things.Values)

{

Console.WriteLine(value);

}

还可以迭代集合中的各个项,把每个项作为一个KeyValuePair<K, V>实例来获取,这与上一章介绍的DictionaryEntry对象相同:

foreach (KeyValuePair<string, int> thing in things)

{

Console.WriteLine("{0} = {1}", thing.Key, thing.Value);

}

对于Dictionary<K, V>要注意的一点是,每个项的键都必须是惟一的。如果要添加的项的键与已有项的键相同,就会抛出ArgumentException异常。所以,Dictionary<K, V>允许把IComparer<K>接口传递给其构造函数,如果要把自己的类用作键,且它们不支持IComparable或IComparable<K>接口,或者要使用非默认的过程比较对象,就必须把IComparer<K>接口传递给其构造函数。例如,在上面的示例中,可以使用不区分大小写的方法比较字符串键:

Dictionary<string, int> things =

new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);

如果使用下面的键,就会得到一个异常:

things.Add("Green Things", 29);

things.Add("Green things", 94);

也可以给构造函数传递初始容量(使用int)或项的集合(使用IDictionary<K,V>接口)。

3. 修改CardLib,以使用泛型集合类

对前几章创建的CardLib项目可以进行简单的修改,即修改Cards集合类,以使用一个泛型集合类,这将减少许多代码。对Cards的类定义需要进行如下修改:

public class Cards : List<Card>, ICloneable

{

...

}

还可以删除Cards的所有方法,但Clone()和CopyTo()除外,因为Clone()是ICloneable需要的方法,而List<Card>提供的CopyTo()版本处理的是Card对象数组,而不是Cards集合。

这里没有列出代码,因为这是很简单的修改,CardLib的更新版本为Ch12CardLib,它和上一章的客户代码包含在本章的下载代码中。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值