1、集合和泛型集合和泛型2 2System.Collections 简介简介 2-1 ID工作档案ID工作档案ID工作档案ID工作档案职员 1职员 2职员 3职员 4Employee 对象的集合对象组中元素个数未知,并且随时可能要循环、添加和移除 集合概念集合概念l如果对象可以提供对相关对象的引用,那么它就是一如果对象可以提供对相关对象的引用,那么它就是一个集合个集合 ,它可以遍历集合中的每个数据项;,它可以遍历集合中的每个数据项;l专业的说法是所有实现了专业的说法是所有实现了System.Collections.IEnumerableSystem.Collections.IEnumerable接
2、口的类的对象都接口的类的对象都是集合。是集合。集合优点集合优点l数组数组Array是固定大小的,不能伸缩;而集合却是是固定大小的,不能伸缩;而集合却是可变长的。可变长的。l数组要声明元素的类型,集合类的元素类型却是数组要声明元素的类型,集合类的元素类型却是object。l数组可读可写不能声明只读数组。集合类可以提供数组可读可写不能声明只读数组。集合类可以提供ReadOnly方法以只读方式使用集合。方法以只读方式使用集合。集合属性集合属性l修改或者获取修改或者获取ArrayListArrayList的容量的容量l使用使用Capacity属性,通过设置该属性的值可以修改属性,通过设置该属性的值可以
3、修改ArrayList的容量;读取该属性的值可以获取的容量;读取该属性的值可以获取ArrayList的容的容量量l当为当为ArrayList对象添加的数据元素的个数超出初始化时指对象添加的数据元素的个数超出初始化时指定的数据项个数时,定的数据项个数时,ArrayList对象的容量还可以自动增长对象的容量还可以自动增长默认增长后的容量为原来的默认增长后的容量为原来的2倍,即数据项的个数为初始化倍,即数据项的个数为初始化时的时的2倍。倍。l常用集合类型常用集合类型 1.ArrayList类类 2.Stack类类 3.Queue类类 4.Hashtable类类 集合类型集合类型7 7集合类型举例集合
4、类型举例-ArrayList的初始化的初始化using System.Collections;ArrayList Students=new ArrayList();ArrayList Teachers=new ArrayList(5)可以指定长度可以指定长度引入命名空间引入命名空间实例化一个对象实例化一个对象常见错误常见错误未引入命名空间未引入命名空间定义时未实例化定义时未实例化l引入System.Collections命名空间l实例化ArrayList对象8 8集合类型举例集合类型举例-ArrayList添加元素添加元素int Add(Object value)/添加一个对象到集合的末尾添加
5、一个对象到集合的末尾ArrayList students=new ArrayList();Student scofield=new Student(Scofield,Genders.Male,28,越狱越狱);students.Add(scofield);创建学员对象创建学员对象添加学员添加学员连续添加三个学员对象连续添加三个学员对象并获取集合元素的数目并获取集合元素的数目返回索引返回索引ArrayList.Count获获取元素数目取元素数目l建立班级学员的集合9 9集合类型举例集合类型举例-访问访问ArrayList元素元素(类型类型)ArrayListindex /按指定索引(下标)取得对
6、象按指定索引(下标)取得对象Student stu1=(Student)students0;stu1.SayHi();按索引取值按索引取值转换为学员对象转换为学员对象ArrayList第一个对第一个对象的索引是象的索引是0需要类型转换需要类型转换1010集合类型举例集合类型举例-删除删除ArrayList的元的元素素ArrayList.Remove(对象名对象名)/删除指定对象名的对象删除指定对象名的对象ArrayList.RemoveAt(index)/删除指定索引的对象删除指定索引的对象ArrayList.Clear()/清除集合内的所有元素清除集合内的所有元素students.Remov
7、eAt(0);students.Remove(zhang);Student leave=(Student)students0;leave.SayHi();通过索引删除对象通过索引删除对象通过指定对象删除通过指定对象删除只剩一个对象只剩一个对象剩余的元素会自动调整索引剩余的元素会自动调整索引取得删除后的第一个对象取得删除后的第一个对象1111集合类型举例集合类型举例-ArrayList 的遍历的遍历/遍历遍历foreach(Object stuo in students)Student stuForeach=(Student)stuo;Console.WriteLine(stuForeach.N
8、ame);通过对象遍历通过对象遍历打印对象的打印对象的Name属性属性Scofield张靓靓张靓靓周杰杰周杰杰l可以使用和数组类似的方式可以使用和数组类似的方式for(int i=0;i students.Count;i+)Student stuFor=(Student)studentsi;Console.WriteLine(stuFor.Name);foreach 方式方式输出结果输出结果类型转换类型转换其他集合类型使用其他集合类型使用l Stack类:栈,后,后进先出。先出。push方法入方法入栈,pop方法出栈方法出栈lQueue类类:队列,先进先出。:队列,先进先出。enqueue方法
9、入队列,方法入队列,dequeue方法出方法出队列。队列。lHashtable类类:哈希表,名:哈希表,名-值对。哈希表是经过优化的,访问下标值对。哈希表是经过优化的,访问下标的对象先散列过。如果以任意类型键值访问其中元素会快于其他的对象先散列过。如果以任意类型键值访问其中元素会快于其他集合。集合。GetHashCode()方法返回一个方法返回一个int型数据,使用这个键的值生型数据,使用这个键的值生成该成该int型数据。哈希表获取这个值最后返回一个索引,表示带有型数据。哈希表获取这个值最后返回一个索引,表示带有给定散列的数据项在字典中存储的位置。给定散列的数据项在字典中存储的位置。泛型的定义
10、泛型的定义l所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型,泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。C#泛型赋予了代码更强的类型安全,更好的复用,更高的效率,更清晰的约束。类型安全 l.NET中的许多语言如C#,C+和VB.NET都是强类型语言。作为一个程序员,当你使用这些语言时,总会期望编译器进行类型安全的检查。然而,当谈到.NET 1.0和1.1中的集合时,它们是无助于类型安全的。比如一个ArrayList的例子,它拥有一个对象集合-这允许你把任何类型的对象放于该ArrayList中。让我们看一下下例中的代码。例例.缺乏类型安全的缺
11、乏类型安全的ArrayList lusing System;using System.Collections;namespace TestAppclass TestSTAThreadstatic void Main(string args)ArrayList list=new ArrayList();list.Add(3);list.Add(4);/list.Add(5.0);int total=0;foreach(int val in list)total=total+val;Console.WriteLine(Total is 0,total);l本例中,我们建立了一个ArrayList的
12、实例,并把3和4添加给它。然后循环遍历该ArrayList,从中取出整型值然后把它们相加。这个程序将产生结果“Total is 7”。现在,如果我们注释掉下面这句:list.Add(5.0);程序将产生异常:哪里出错了呢?记住ArrayList拥有一个集合的对象。当你把3加到ArrayList上时,你已把值3装箱了。当你循环该列表时,你是把元素拆箱成int型。然而,当你添加值5.0时,你在装箱一个double型值。在第17行,那个double值被拆箱成一个int型。这就是失败的原因。作为一个习惯于使用语言提供的类型安全作为一个习惯于使用语言提供的类型安全的程序员,希望这样的问题在编译期间浮的程
13、序员,希望这样的问题在编译期间浮出水面,而不是在运行时刻。这正是泛型出水面,而不是在运行时刻。这正是泛型产生的原因。产生的原因。泛型泛型l泛型是泛型是 2.0 版版 C#语言和公共语言运行库语言和公共语言运行库(CLR)中的一个新中的一个新功能。功能。l泛型将类型参数的概念引入泛型将类型参数的概念引入.NET Framework,不致引入运,不致引入运行时强制转换或装箱操作的成本或风险。行时强制转换或装箱操作的成本或风险。(强类型化)(强类型化)l使用泛型类型可以最大限度地重用代码、保护类型的安全以使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。泛型最常见的用途是创建集合类。及
14、提高性能。泛型最常见的用途是创建集合类。l.NET 2.0的的System.Collections.Generics 命名空间包含了命名空间包含了泛型集合定义。各种不同的集合泛型集合定义。各种不同的集合/容器类都被容器类都被“参数化参数化”了。为了。为使用它们使用它们,只需简单地指定参数化的类型即可。只需简单地指定参数化的类型即可。l编程时需要引入编程时需要引入System.Collection.Generic名称空间,最名称空间,最为常用的有以下两个为常用的有以下两个:lList T类型对象的集合。使用方法与类型对象的集合。使用方法与ArrayList类似,类似,但它不仅比但它不仅比Arra
15、yList更安全,而且明显地更加快速。更安全,而且明显地更加快速。lDictionary V类型的项与类型的项与K类型的键值相关的集类型的键值相关的集合,可以理解合,可以理解Dictionary 是是 Hashtable的泛型版本的泛型版本。泛型泛型Listl创建创建T T类型对象的泛型集合语法为:类型对象的泛型集合语法为:lList List 泛型对象名泛型对象名=new List();=new List();l添加泛型添加泛型ListList的数据项的数据项lAdd()Add():向列表尾部添加,输入参数为:向列表尾部添加,输入参数为T T类型数据类型数据lAddRangeAddRange
16、)():向列表尾部添加,输入参数为:向列表尾部添加,输入参数为T T类类型对象组型对象组lInsert()Insert():向指定位置添加,输入数据为位置索:向指定位置添加,输入数据为位置索引和要添加的对象(引和要添加的对象(T T类型)类型)例:类型安全的泛型列表例:类型安全的泛型列表lListint aList=new Listint();aList.Add(3);aList.Add(4);/aList.Add(5.0);int total=0;foreach(int val in aList)total=total+val;Console.WriteLine(Total is 0,to
17、tal);l在上例中,我们编写了一个泛型的列表的例子,在尖括号内指定参数类型为int。该代码的执行将产生结果“Total is 7”。现在,如果去掉语句doubleList.Add(5.0)的注释,将得到一个编译错误。编译器指出它不能发送值5.0到方法Add(),因为该方法仅接受int型。不同于前例,这里的代码实现了类型安全。泛型集合举例泛型集合举例 public class Employee:IComparable public int ID;public string name;public string department;public Employee(int iID,string
18、sName,string sDepartment)ID=iID;name=sName;department=sDepartment;public int CompareTo(object obj)if(obj is Employee)Employee anotherEmployee=obj as Employee;return this.ID-anotherEmployee.ID;else return 0;输出结果:ID:1,Name:Liu,Department:DataID:2,Name:Li,Department:NetID:3,Name:Yu,Department:NetID:4,N
19、ame:Jiang,Department:TrainID:5,Name:Jia,Department:Soft class Program static void Main(string args)List aList=new List();aList.Add(new Employee(2,Li,Net);aList.Add(new Employee(3,Yu,Net);aList.Add(new Employee(1,Liu,Data);aList.Add(new Employee(5,Jia,Soft);aList.Add(new Employee(4,Jiang,Train);aList
20、Sort();/使用使用CompareTo进行排序进行排序 /逐个从列表中取出每一个对象逐个从列表中取出每一个对象 foreach(Employee iItem in aList)Console.WriteLine(ID:0,Name:1,Department:2,iItem.ID,iItem.name,iItem.department);2323 List 与与 ArrayList通过索引删除元素通过索引删除元素添加对象方法相同添加对象方法相同通过索引访问集合的元素通过索引访问集合的元素相同点相同点需要装箱拆箱需要装箱拆箱无需装箱拆箱无需装箱拆箱可以增加任何类型可以增加任何类型增加元素时类
21、型严格检查增加元素时类型严格检查不同点不同点ArrayListList异同点异同点l访问访问 List 与与 ArrayList 的对比的对比DictionarylDictionaryDictionary常用的属性和方法有以下一些常用的属性和方法有以下一些Keys属性属性获取包含获取包含 Dictionary 中的键的集合中的键的集合 Values属性属性获取包含获取包含 Dictionary 中的值的集合中的值的集合 Add()将指定的键和值添加到字典中将指定的键和值添加到字典中 Clear()从从 Dictionary 中移除所有的键和值中移除所有的键和值 Remove()从从 Dicti
22、onary 中移除所指定的键的值中移除所指定的键的值 泛型类 l 泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等。像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。l 一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。例例.创建一个泛型类创建一个泛型类lusing System;using System.Collections.Generic;using System.Text;namespace CLRSupportExamplepublic
23、 class MyListTprivate static int objCount=0;public MyList()objCount+;public int Countgetreturn objCount;lusing System;using System.Collections.Generic;using System.Text;namespace CLRSupportExampleclass SampleClass class Programstatic void Main(string args)MyListint myIntList=new MyListint();MyListin
24、t myIntList2=new MyListint();MyListdouble myDoubleList=new MyListdouble();MyListSampleClass mySampleList=new MyListSampleClass();Console.WriteLine(myIntList.Count);Console.WriteLine(myIntList2.Count);Console.WriteLine(myDoubleList.Count);Console.WriteLine(mySampleList.Count);Console.WriteLine(new My
25、Listsampleclass().Count);Console.ReadLine();l该例中,我们创建了一个称为MyList泛型类。为把它参数化,简单地插入了一个尖括号。在内的T代表了实际的当使用该类时要指定的类型。在MyList类中,定义了一个静态字段objCount。我们在构造器中增加它的值。因此能发现使用我们的类的用户共创建了多少个那种类型的对象。属性Count返回与被调用的实例同类型的实例的数目。在Main()方法,我们创建了MyListint的两个实例,一个MyListdouble的实例,还有两个MyListSampleClass的实例-其中SampleClass是我们已定义了的
26、类。下面是程序输出的结果。下面是程序输出的结果。l22112 泛型方法l除了有泛型类,也可以有泛型方法。泛型方法可以是任何类的一部分。例例.一个泛型方法一个泛型方法lpublic class Programpublic static void CopyT(ListT source,ListT destination)foreach(T obj in source)destination.Add(obj);static void Main(string args)Listint lst1=new Listint();lst1.Add(2);lst1.Add(4);Listint lst2=new
27、 Listint();Copy(lst1,lst2);Console.WriteLine(lst2.Count);lCopy()方法就是一个泛型方法,它与参数化的类型T一起工作。当在Main()中激活Copy()时,编译器根据提供给Copy()方法的参数确定出要使用的具体类型。类型参数的约束类型参数的约束 l在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下面列出了六种类型的约束:lT:结构:结构 l类型参数必须是值类型。可以
28、指定除类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。以外的任何值类型。lT:类:类 l类型参数必须是引用类型;这一点也适用于任何类、接口、委托类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。或数组类型。lT:new()l类型参数必须具有无参数的公共构造函数。当与其他约束一起使类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,用时,new()约束必须最后指定。约束必须最后指定。lT:l类型参数必须是指定的基类或派生自指定的基类。类型参数必须是指定的基类或派生自指定的基类。lT:l类型参数必须是指定的接口或实现指定的接口。可以指定多个接类型参
29、数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。口约束。约束接口也可以是泛型的。lT:U l为为 T 提供的类型参数必须是为提供的类型参数必须是为 U 提供的参数或派生自为提供的参数或派生自为 U 提供的提供的参数。这称为裸类型约束。参数。这称为裸类型约束。使用约束的原因使用约束的原因 l如果要检查泛型列表中的某个项以确定它是否有效,或者将它与其他某个项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。这种保证是通过对泛型类定义应用一个或多个约束获得的。例如,基类约束告诉编译器:仅此类型的对象或从此类型
30、派生的对象才可用作类型参数。一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。约束是使用上下文关键字 where 应用的。下面的代码示例演示可通过应用基类约束添加到 GenericList 类的功能。lpublic class Employeell private string name;l private int id;l public Employee(string s,int i)l l name=s;l id=i;l l public string Namel l get return name;l set name=value;l l public int IDl l
31、get return id;l set id=value;l llpublic class GenericList where T:Employeell private class Nodel l private Node next;l private T data;l public Node(T t)l l next=null;l data=t;l l public Node Nextl l get return next;l set next=value;l l public T Datal l get return data;l set data=value;l l l lprivate
32、 Node head;l public GenericList()/constructorl l head=null;l l public void AddHead(T t)l l Node n=new Node(t);l n.Next=head;l head=n;l l public IEnumerator GetEnumerator()l l Node current=head;l while(current!=null)l l yield return current.Data;l current=current.Next;l l l lpublic T FindFirstOccurre
33、nce(string s)l l Node current=head;l T t=null;l while(current!=null)l l /The constraint enables access to the Name property.l if(current.Data.Name=s)l l t=current.Data;l break;l l elsel l current=current.Next;l l l return t;l ll约束使得泛型类能够使用约束使得泛型类能够使用 Employee.Name 属性,因为类型为属性,因为类型为 T 的所有项都保证是的所有项都保证是
34、 Employee 对象或从对象或从 Employee 继承的对象。继承的对象。l可以对同一类型参数应用多个约束,并且约束可以对同一类型参数应用多个约束,并且约束自身可以是泛型类型,如下所示:自身可以是泛型类型,如下所示:lclass EmployeeList where T:Employee,IEmployee,System.IComparable,new()/.继承与泛型继承与泛型 l一个使用参数化类型的泛型类,象MyClass1T,称作开放结构的泛型。一个不使用参数化类型的泛型类,象MyClass1int,称作封闭结构的泛型。你可以从一个封闭结构的泛型进行派生;也就是说,你可以从另外一个
35、称为MyClass1的类派生一个称为MyClass2的类,就象:lpublic class MyClass2T:MyClass1int l你也可以从一个开放结构的泛型进行派生,如果类型被参数化的话,如:public class MyClass2T:MyClass2T是有效的,但是 public class MyClass2T:MyClass2Y 是无效的,这里Y是一个被参数化的类型。非泛型类可以从一个封闭结构的泛型类进行派生,但是不能从一个开放结构的泛型类派生。Assignmentl(1 1)添加一个继承自)添加一个继承自ListList的类的类ListEXListEX,假设,假设T T类型不
36、提供类型不提供对象比较的方法,为该类添加方法对象比较的方法,为该类添加方法CountAllCountAll()(),输入参数为,输入参数为T T类型类型对象,该方法用于计算指定参数出现在对象,该方法用于计算指定参数出现在ListEXListEX中的次数;假设中的次数;假设T T类型实现了接口类型实现了接口IComparableIComparable,提供了,提供了Compare()Compare()方法,可以对两方法,可以对两个个T T类型的对象进行大小的比较,修改类型的对象进行大小的比较,修改CountAllCountAll()()方法,先将泛型方法,先将泛型中的数据元素排序,再计算指定参数
37、出现在中的数据元素排序,再计算指定参数出现在ListEXListEX中的次数。中的次数。l(2 2)在主函数中创建该泛型类的实例,并为该类添加)在主函数中创建该泛型类的实例,并为该类添加1010个元素,个元素,1 1、2 2、2 2、3 3、3 3、3 3、4 4、4 4、4 4和和4 4;分别调用(;分别调用(2 2)中的两个)中的两个CountAllCountAll()()方法计算方法计算1 1、2 2、3 3和和4 4在集合中出现的次数,并输出。在集合中出现的次数,并输出。l(3 3)试着在()试着在(2 2)中的两个)中的两个CountAllCountAll()()方法中添加计时功能,再方法中添加计时功能,再次运行程序,体会排序是否提高了程序的时间效率。次运行程序,体会排序是否提高了程序的时间效率。