明天的明天 永远的永远 未知的一切 我与你一起承担 ??

是非成败转头空 青山依旧在 几度夕阳红 。。。
  博客园  :: 首页  :: 管理

定义泛型集合的命名空间:System.Collections.Generic

Posted on 2009-05-20 21:05  且行且思  阅读(12774)  评论(0编辑  收藏  举报

本章导读

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类的属性及其说明

   

属性说明

Count

排序字典中键/值对的数量

Item

指定键的值

Keys

排序字典中的键的集合

Values

排序字典中的值的集合

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的前提。