本章导读
System.Collections.Generic和System.Collections集合的结构有很多相似之处,不同的是“Generic”提供的都是泛型集合,而“Collections”中的集合并不都支持泛型。
System.Collections.Generic是在C# 2.0中新加的命名空间,主要用来创建强类型集合,以提高类型安全和操作性能。
本章的讲解流程如图8-1所示。
图8-1 System.Collections.Generic的讲解流程
8.1 System.Collections.Generic简介
本节主要介绍System.Collections.Generic包含的内容。泛型是C# 2.0中新增的一个命名空间,其设计目的主要是为了保障类型转换的安全,常应用在集合中。
8.1.1 什么是泛型
泛型的意义在于,通过参数化类型来实现在同一份代码上对多种数据类型的操作。这种解释比较抽象,简单来讲,泛型就是利用参数化类型将类型抽象化,通常称为“类型多态”。
泛型使用“< >”将类型参数化,其中被尖括号包装的类型,必须是从System.Object继承的共有成员。泛型使用语法如下:
List<string> dinosaurs = new List<string>();
泛型的优点如下:
(1)更强的类型安全。
(2)更好的复用,因为类型其实是一个参数。
(3)更高的效率。
(4)更清晰的约束。
8.1.2 System.Collections.Generic概述
System.Collections.Generic用来管理泛型集合。此命名空间也包含System.Collections中的一些集合类,区别在于Generic提供了哪些集合的泛型版本。另外,System.Collections中某些集合是不具备泛型版本的。虽然Generic包含的是集合的泛型版本,但其基本操作方法与Collections中的集合类似。
Generic命名空间只有在C# 2.0中才有,而且泛型也是2.0中很关键的类型方式,学习本章的内容必须具备一定的集合知识。
8.1.3 System.Collections.Generic命名空间内的类组成
集合是数据操作的关键,而泛型集合提高了操作的安全性。System.Collections.Generic命名空间内的类,负责管理并提供泛型集合的一些常用功能。表8-1列出了泛型集合常用的类及其说明。
表8-1 System.Collections.Generic命名空间内常用的类及其说明
类 名 |
类 说 明 |
Dictionary |
键/值对集合 |
LinkedList |
双向链表 |
List |
是ArrayList的泛型等效类,属于动态数组 |
Queue |
队列,先进先出的集合 |
SortedDictionary |
可以排序的键/值对集合 |
SortedList |
同SortedDictionary类似,可排序的键/值对集合 |
Stack |
栈,后进先出的集合 |
System.Collections.Generic命名空间内的接口也非常重要,是自定义和扩展标准集合的关键。表8-2罗列的是System.Collections.Generic命名空间内的常用接口,本章所介绍的大部分类都继承这些接口。
表8-2 System.Collections.Generic命名空间内常用的接口及其说明
接 口 名 |
接口说明 |
ICollection |
Generic命名空间中类的基接口 |
IComparer |
此接口定义比较对象的方法 |
IDictionary |
表示键/值对的泛型集合 |
IEnumerable |
定义枚举数 |
IEnumerator |
支持在泛型集合上进行简单迭代 |
IEqualityComparer |
此接口定义“相等”比较的方法 |
IList |
可按照索引单独访问的一组对象 |
虽然System.Collections.Generic命名空间包含了多个泛型集合类,但这些类除去泛型使用语法不同外,其他操作方法和属性与System.Collections中的类相似。同时鉴于本书的篇幅限制,所以本章只介绍“Dictionary”,“LinkedList”和“SortedDictionary”类,而“Queue”,“SortedList”和“Stack”类的使用,读者可参考介绍“System.Collections”命名空间的有关章节。泛型集合中还有一个“List”类,其实就是一个泛型的ArrayList,学习List可以参考非泛型集合中对ArrayList的介绍。
8.2 泛型字典集合:Dictionary类
Dictionary类又称为字典集合,本节介绍其内部构造及使用语法。在使用该类前,一定要弄清楚什么是字典,以及字典集合具备什么样的优点。
8.2.1 功能说明
Dictionary类中保存的是键/值对集合,值可以为任意类型数据,其操作方法和构造都与Hashtable类相似,可以把Dictionary看成是Hashtable类的泛型版本。
Dictionary类也是通过“键”来检索数据的,这是提高检索速度的关键。Dictionary类默认初始容量为3。在实际应用中,如果可以测算出键/值对的数量,那么最好在定义Dictionary类时,指定其初始容量,这样在进行添加和移除操作时,就减少了大小调整这一环节。
8.2.2 语法定义
Dictionary类的语法定义如下:
[SerializableAttribute]
[ComVisibleAttribute(false)]
public class Dictionary<TKey,TValue> : IDictionary<TKey,TValue>, ICollection<KeyValuePair<TKey,TValue>>,
IEnumerable<KeyValuePair<TKey,TValue>>, IDictionary, ICollection, IEnumerable,
ISerializable, IDeserializationCallback
此处需注意ICollection<KeyValuePair<TKey,TValue>>和ICollection的区别:一个是泛型集合接口,一个是非泛型集合接口。
Dictionary有7种构造方法,其中最常用的是默认不带任何参数的方法,使用语法如下:
Dictionary<string, string> openWith =new Dictionary<string, string>();
之所以说泛型是一种类型多态,是因为在定义泛型集合的时候,可以指定任意类型,如下所示的构造方法都正确:
Dictionary<int, string> dic1 =new Dictionary<int, string>();
Dictionary<int, int> dic1 = new Dictionary<int, int>();
Dictionary<int, object> dic1 = new Dictionary<int, object>();
8.2.3 属性详解
Dictionary类的属性主要用来获取集合中的元素,表8-3详细列出了这些属性及其说明。
表8-3 Dictionary类的属性及其说明
属 性 名 |
属性说明 |
Comparer |
判断字典中的键是否相等 |
Count |
字典中的键/值对数量 |
Item |
字典中指定键对应的值 |
Keys |
字典中键的集合 |
Values |
字典中值的集合 |
8.2.4 方法详解
Dictionary类的方法用来操作字典中的键/值对,如添加、删除等。表8-4列出的是Dictionary类中常用的方法及其说明。
表8-4 Dictionary类常用的方法及其说明
方法名称 |
方法说明 |
Add |
添加键/值对 |
Clear |
清空键/值对 |
ContainsKey |
判断字典中是否包含指定的主键 |
ContainsValue |
判断字典中是否包含指定的值 |
Remove |
移除指定的键/值对 |
TryGetValue |
获取字典中的指定值 |
从上述方法中可以看出,泛型集合的方法与非泛型集合的方法基本相似。其实两者最主要的区别还是类型的参数化,而不是属性和方法的差异。
下面的代码演示如何使用Dictionary类中的方法,请仔细阅读注释。
Dictionary<int, string> dic1 =new Dictionary<int, string>();
dic1.Add(1, "北京");//添加键/值对
dic1.Add(2, "上海");
Response.Write("是否包含键‘2’"+dic1.ContainsKey(2)) ;
Response.Write("是否包含值‘上海’"+dic1.ContainsValue("上海")) ;
string myValue = "";
dic1.TryGetValue(1, out myValue);//获取指定键的值
8.2.5 典型应用:用Dictionary缓存数据库命令参数
泛型类的主要优点是类型安全,这主要表现在当从集合中获取数据时,无须再进行显式类型转换,因为其已经用“<>”定义了类型参数。如获取HashTable中保存的命令参数,必须执行以下转换:
SqlParameter[] cachedParms = (SqlParameter[])parmCache[cacheKey];
而Dictionary泛型类,则可以不使用这种转换,而是直接用如下代码实现上述操作:
SqlParameter[] cachedParms =parmCache[cacheKey];
本例的目的是使用Dictionary实现命令参数的缓存,读者可对比“Hashtable”类的实例,了解泛型字典集合与非泛型字典集合在使用上的差异。本例的演示步骤如下。
打开VS2005,新建一个网站,命名为“DictionarySample”。
打开默认生成的Default.aspx页。在视图中添加两个“ListBox”控件。
按F7键切换到页面的代码视图。添加对数据库命名空间和集合命名空间的引用,代码如下:
using System.Data.SqlClient;
using System.Collections.Generic;
定义静态全局变量“parmCache”,用来缓存命令参数,定义语法如下:
//定义缓存参数的泛型字典集合,注意static
private static Dictionary<string, SqlParameter[]> parmCache = new Dictionary<string, SqlParameter[]>();
在代码中添加两个方法“CacheParameters”和“GetCachedParameters”,其中CacheParameters用来添加键/值对到泛型字典中,GetCachedParameters用来获取泛型字典表中某键的值。两个方法的代码如下:
//定义设置字典表参数的方法
public static void CacheParameters(string cacheKey, params SqlParameter[] commandParas)
{
parmCache[cacheKey] = commandParas;
}
//定义获取字典表参数的方法
public static SqlParameter[] GetCachedParameters(string cacheKey)
{
SqlParameter[] cachedParms = parmCache[cacheKey];
if (cachedParms == null)//判断是否为空
return null;
return cachedParms;
}
在页面的Page_Load事件中,为哈希表设置三个键/值对,并在ListBox1中显示字典表中所有的键。代码如下所示,注意黑体部分,其获取键值的方法与哈希表不同。
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//设计3个参数集合
SqlParameter[] parmsA = new SqlParameter[] { new SqlParameter("@tabAid",SqlDbType.Int),
new SqlParameter("@tabAname",SqlDbType.NVarChar,20) };
SqlParameter[] parmsB = new SqlParameter[] { new SqlParameter("@tabBid",SqlDbType.Int),
new SqlParameter("@tabBname",SqlDbType.NVarChar,20), new SqlParameter("@tabBdate",SqlDbType.DateTime) };
SqlParameter[] parmsC = new SqlParameter[] { new SqlParameter("@tabCid",SqlDbType.Int),
new SqlParameter("@tabCname",SqlDbType.NVarChar,20) };
//添加参数集合到哈希表中
CacheParameters("TableA", parmsA);
CacheParameters("TableB", parmsB);
CacheParameters("TableC", parmsC);
//获取所有键
Dictionary<string, SqlParameter[]>.KeyCollection mykeys = parmCache.Keys;
foreach (string strkey in mykeys)
{//让ListBox1显示所有的键
ListBox1.Items.Add(strkey);
}
}
}
本例的功能是当选择ListBox1中的键时,在ListBox2中显示此键对应的值,也就是所设置的参数集合。这需要在ListBox1的“SelectedIndexChanged”事件中完成此功能,代码如下:
protected void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
{
//通过键,获取缓存的参数集合
SqlParameter[] parms = GetCachedParameters(ListBox1.SelectedValue);
ListBox2.Items.Clear();//清空列表中的旧数据
foreach (SqlParameter parm in parms)
{//遍历参数集合,显示所有的参数
ListBox2.Items.Add(parm.ParameterName);
}
}
注意:ListBox默认的“AutoPostBack”属性为false,为了实现数据的回传,必须将其设置为true。
按Ctrl+S组合键保存所有的代码,按F5键运行程序,可以发现ListBox1中罗列的是字典表中的键。当单击ListBox1中的某项时,ListBox2会显示对应的参数列表。
8.3 双向链表集合:LinkedList类
LinkedList类管理的是双向链表,本节将介绍其概念、组成及应用。这些链表并不像C++中的那些链表一样,学起来特别抽象。C#中的链表类,对所有基本功能都进行了封装,使其应用更简单。
8.3.1 功能说明
链表是C语言时期就存在的一种数据结构,是以链接方式存储的线性表,其中数据保存在线性表的节点中。节点内还有一个域,用来存放后继节点的指针,这种节点内只有一个指针域的被称为单向链表。如果节点内有两个指针域,一个指向前一个节点,一个指向后一个节点,那么这种链表被称为双向链表,本节介绍的LinkedList属于双向链表。
LinkedList类中的节点,属于“LinkedListNode”类型,由于LinkedList属于双向链表,所以其节点内包含两个指针域,用来获取前一个和后一个节点,在实际应用中,LinkedListNode节点类型包含属性“Next”和“Previous”代替了这两个指针域。
8.3.2 语法定义
LinkedList类的语法定义如下:
[SerializableAttribute]
[ComVisibleAttribute(false)]
public class LinkedList<T> : ICollection<T>, IEnumerable<T>,
ICollection, IEnumerable, ISerializable, IDeserializationCallback
LinkedList继承了ISerializable和IDeserializationCallback,支持序列化和反序列化。
LinkedList有三种构造方法,最常用的是不带任何参数的构造方法,其构造语法如下:
LinkedList<string> mylink1 = new LinkedList<string>();
8.3.3 属性详解
LinkedList类的属性主要是获取链表的节点,表8-5详细列出了这些属性及其说明。
表8-5 LinkedList类的属性及其说明
属 性 名 |
属性说明 |
Count |
双向链表中的节点数 |
First |
双向链表中的第一个节点 |
Last |
双向链表中的最后一个节点 |
8.3.4 方法详解
LinkedList类的方法用来操作双向链表中的节点,如添加、移除等。表8-6详细列出了这些方法及其说明。
表8-6 LinkedList类的方法及其说明
方法名称 |
方法说明 |
AddAfter |
链表的当前节点后添加新节点 |
AddBefore |
链表的当前节点前添加新节点 |
AddFirst |
在链表的第一个节点前添加新节点 |
AddLast |
在链表的最后一个节点后添加新节点 |
Clear |
清空链表中的节点 |
Contains |
判断链表是否包含某值 |
Find |
查找指定值所在的节点。查找到一个后便结束查找 |
FindLast |
查找指定值所在的节点。返回最后一个包含此值的节点 |
Remove |
移除链表中指定的节点或值 |
RemoveFirst |
移除链表中第一个节点 |
RemoveLast |
移除链表中最后一个节点 |
下面的代码演示如何使用LinkedList类中的方法,由于方法比较多,请仔细阅读注释。
LinkedList<string> mylink1 = new LinkedList<string>();//初始化
mylink1.AddFirst("this ");//添加节点
mylink1.AddLast("is ");
mylink1.AddLast(" Beijing");
LinkedListNode<string> mynode = mylink1.Find("is ");//检索节点
Response.Write("上个节点的值:" + mynode.Previous.Value+"<br />");//上一个节点
Response.Write("下个节点的值:" + mynode.Next.Value + "<br />");//下一个节点
mylink1.RemoveLast();//移除节点
8.3.5 典型应用:用LinkedList实现记录的翻页
虽然C# 2.0提供了很多功能强大的翻页控件库,但其速度仍然是个瓶颈。本例不使用任何翻页控件,而是使用LinkedList实现一个记录的翻页,这些记录都比较简单。在实际项目中,也可以考虑使用这种方法,实现复杂数据的翻页处理。
实例的演示步骤如下所述。
打开VS2005,新建一个网站,命名为“LinkedListSample”。
在“App_Code”目录下,添加一个类“UserInfo.cs”,用来自定义记录类型。代码设计如下:
using System;
public class UserInfo
{
private string _name;
private string _city;
public UserInfo(string name, string city)
{
_name = name;
_city = city;
}
public string Name
{
set { _name = value; }
get { return _name; }
}
public string City
{
set { _city = value; }
get { return _city; }
}
}
按Ctrl+S组合键保存自定义的记录类型。
打开Default.aspx页。在视图中添加两个文本框,用来显示人名和城市;再添加两个按钮,用来实现上、下翻页,同时设计两个按钮的CommandName属性分别为“Next”和“Previous”。
将两个按钮的“Command”事件指向同一事件“Button1_Command”,代码如下:
protected void Button1_Command(object sender, CommandEventArgs e)
{
UserInfo tmpUser=new UserInfo(txtName.Text.Trim(), txtCity.Text.Trim());
LinkedListNode<UserInfo> mynode = myData.Find(tmpUser);
if (e.CommandName == "Next")
{//下一页
txtName.Text = mynode.Next.Value.Name;
txtName.Text = mynode.Next.Value.City;
}
else
{ //上一页
txtName.Text = mynode.Previous.Value.Name;
txtName.Text = mynode.Previous.Value.City;
}
}
引用泛型集合命名空间“System.Collections.Generic”。定义双向链表集合,代码如下:
private static LinkedList<UserInfo> myData = new LinkedList<UserInfo>();//初始化
在“Page_Load”事件中,为链表赋值,同时显示当前的人名和城市,代码如下:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
myData.AddLast(new UserInfo("张三","北京"));//设置数据
myData.AddLast(new UserInfo("王三", "上海"));
myData.AddLast(new UserInfo("张明", "广州"));
txtName.Text = "张三";//设置默认值
txtCity.Text = "北京";
}
}
按Ctrl+S组合键保存所有的设计,再按F5键运行程序,单击上、下翻页按钮,测试记录的翻页情况。
8.4 排序泛型字典集合:SortedDictionary类
SortedDictionary类是一个按键排序的字典集合,本节介绍SortedDictionary的主要功能及其使用方法。在使用时,一定要特别注意Sorted的作用。
8.4.1 功能说明
SortedDictionary类是一个可以按键排序的Dictionary类,其功能类似于非泛型集合中的SortedList。在SortedDictionary类中,元素的键值是不允许改变的,系统自动通过键进行排序。
SortedDictionary类与SortedList类的区别如下。
— 内存的使用:SortedList类使用的内存比SortedDictionary类少。
— 填充速度:比较前提是要填充的列表已经排序,并一次性填充到字典中,此时SortedList的速度比SortedDictionary快。
— 添加和删除的速度:当单个在字典中添加和删除元素时,SortedDictionary具备更快的速度。
8.4.2 语法定义
SortedDictionary类的语法定义如下:
[SerializableAttribute]
public class SortedDictionary<TKey,TValue> : IDictionary<TKey,TValue>, Icollection <KeyValuePair<TKey,TValue>>,
IEnumerable<KeyValuePair<TKey,TValue>>, IDictionary, ICollection, IEnumerable
其中SortedDictionary继承ICollection和IDictionary的泛型接口。
SortedDictionary有4种构造方法,通常使用不带参数的方法来构造SortedDictionary的实例,使用语法如下:
SortedDictionary<string, string> myDic =new SortedDictionary<string, string>();
注意:类型参数可为继承System.Object的任意类型。
8.4.3 属性详解
SortedDictionary类的属性主要是获取集合中的键/值对,表8-7详细列出了这些属性及其说明。
表8-7 SortedDictionary类的属性及其说明
8.4.4 方法详解
SortedDictionary类的方法用来对集合内的元素进行操作,如添加、删除等,表8-8列出的是SortedDictionary类中常用的方法及其说明。
表8-8 SortedDictionary类常用的方法列表
方法名称 |
方法说明 |
Add |
在集合中添加键/值对 |
Clear |
清空集合中的键/值对 |
ContainsKey |
判断指定的键是否包含在集合中 |
ContainsValue |
判断指定的值是否包含在集合中 |
Remove |
从集合中移除指定的键/值对 |
TryGetValue |
获取与指定键相关联的值 |
下面的代码演示如何使用SortedDictionary类中的方法:
SortedDictionary<int,string> mydic=new SortedDictionary<int,string>();//初始化
mydic.Add(1,"海淀"); //添加元素
mydic.Add(2,"西城");
if (!mydic.ContainsValue("东城")) //判断是否包含指定的值
mydic.Add(3, "东城"); //添加元素
mydic.Remove(1); //移除键为1的元素
string myvalue = "";
mydic.TryGetValue(2,out myvalue); //获取键为2的元素的值
8.4.5 典型应用:使用SortedDictionary实现ListBox的排序
本例利用SortedDictionary类的自动排序功能,实现ListBox控件的自动排序。要实现的功能是:当用户添加内容到ListBox中时,后台先将内容添加到SortedDictionary类的实例中,此时内容会自动排序,然后将ListBox的数据源绑定到SortedDictionary类的实例,这样就实现了ListBox控件的排序。
本实例的演示步骤如下所述。
打开VS2005,新建一个网站,命名为“SortedDictionarySample”。
打开Default.aspx页,在视图中添加一个ListBox,用来显示排序后的数据;两个TextBox,用来允许用户添加键/值对;一个按钮,用来实现用户的添加操作。
按F7键切换到代码视图,添加对泛型集合的命名空间“System.Collections.Generic”的引用。
定义页面的全局变量“mydic”,代码如下:
static SortedDictionary<string, string> mydic = new SortedDictionary<string, string>();
在第一次加载页面时,需要显示ListBox中已有的内容,代码如下:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
mydic.Add("1", "首长");//添加集合中的元素
mydic.Add("3", "政委");
ListBox1.DataSource = mydic;//绑定数据源
ListBox1.DataBind();
}
}
当用户输入键/值对,单击“添加”按钮时,需要实现内容的添加和排序,实现代码如下:
protected void btnAdd_Click(object sender, EventArgs e)
{
try
{
mydic.Add(txtKey.Text, txtValue.Text); //添加元素,自动排序
ListBox1.DataSource = mydic; //绑定数据源
ListBox1.DataBind();
}
catch (Exception ex)
{ Response.Write("要添加的键重复!"); }
}
按Ctrl+S组合键保存代码,按F5键运行程序,并在“键”文本框内输入“2”,在“值”文本框内输入“将军”。单击“添加”按钮,可以发现ListBox进行了排序,并重新显示了排序后的数据。
8.5 小结
本章介绍的是泛型集合命名空间System.Collections.Generic的内容,其中最主要的是“泛型”的意义。类型安全和约束是泛型集合的两大特点。
System.Collections.Generic和System.Collections中的很多接口和类都相似,掌握好System.Collections中的接口和类,是学习System.Collections.Generic的前提。