1、第2章 集合与泛型 2.1 集合概述 所谓集合是特殊元素们的一种聚合。集合的元素被称为是成员。集合有两个最重要的属性,一个是集合成员都是无序的,另一个则是集合的成员不会出现超过一次。在计算机科学领域内集合扮演着非常重要的角色,但是不把集合包含作为C#语言的一种数据结构。 2.1.1 集合的定义 人们把集合定义成相关成员的无序聚集,而且集合中的成员不会出现超过一次。集合书写成用一对闭合大括号包裹成员列表的形式,例如{0,1,2,3,4,5,6,7,8,9}。只要全部成员只书写一次,就可以按照任意顺序书写集合,所以此前的集合实例还可以写成{9,8,7,6,5,4,3,2,1,0}或其他任意
2、成员组合的形式。 为了使用集合需要知道一些有关集合的定义。 1. 不包含任何成员的集合称为空集合。全域是所有可能成员的集合。 2. 如果两个集合包含完全一样的成员,那么就认为这两个集合相等。 3. 如果第一个集合的全部成员都包含在第二个集合内,就认为第一个集合是第二个集合的子集。 2.1.2 集合的操作 下面描述了在集合上执行的基本操作。 1. 联合:由第一个集合中的所有成员和第二个集合中不包含在第一个集合的成员组成的新集合。 2. 交叉:由既属于第一个集合又属于第二个集合的成员组成的新集合。 3. 差异:由属于第一个集合但不属于第二个集合的成员组成的新集合。 2.2 集合
3、类 ArrayList是与数组相当的集合类。还有其他类型的集合:队列、栈、有序表和哈希表,如下所示: l ArrayList 动态数组 l Queue 队列 l Stack 栈 l BitArray 位数组 l Hashtable 散列表 l SortedList 有序表 集合类位于System.Collections命名空间,集合类可以组合为集合,存储Object类型的元素。 2.3 集合的一种实现 HashTable类是.NET框架类库中较为有效的数据结构之一,它的存取速度较其它类而言要快。下面就以散列表作为存储结构来实现一个集合类,具体代码如下: usin
4、g System; using System.Collections; //集合 public class Set { private Hashtable data; public Set() { data = new Hashtable(); } //添加成员。如果成员不在集合内,则添加到集合中 public void Add(Object item) { if (!data.ContainsValue(item)) { d
5、ata.Add(Hash(item), item); } } //根据成员各字符的ASCII码值计算散列值(在散列表中数据项以键值对形式存在) private string Hash(Object item) { char[] chars; string s = item.ToString(); int hashValue = 0; chars = s.ToCharArray(); for (int i = 0; i <= chars.GetU
6、pperBound(0); i++) { hashValue += (int)chars[i]; } return hashValue.ToString(); } //删除成员 public void Remove(Object item) { data.Remove(Hash(item)); } //成员数量 public int Size { get {
7、 return data.Count; } } //求并集。由第一个集合中的所有成员和第二个集合中不包含在第一个集合的成员组成的新集合。 public Set Union(Set aSet) { Set tempSet = new Set(); foreach (Object hashObject in data.Keys) { tempSet.Add(this.data[hashObject]); } forea
8、ch (Object hashObject in aSet.data.Keys) { if (!(this.data.ContainsKey(hashObject))) { tempSet.Add(aSet.data[hashObject]); } } return tempSet; } //求交集。由既属于第一个集合又属于第二个集合的成员组成的新集合。 public Set Intersection
9、Set aSet) { Set tempSet = new Set(); foreach (Object hashObject in data.Keys) { if (aSet.data.Contains(hashObject)) { tempSet.Add(aSet.data[hashObject]); } } return tempSet; } //判断子集。
10、如果第二个集合大于第一个集合,并且第一个集合中所有成员都包含在第二个集合中,则第一个集合是第二个集合的子集。 public bool Subset(Set aSet) { if (this.Size > aSet.Size) return false; foreach (Object key in this.data.Keys) { if (!(aSet.data.Contains(key))) return false; } return true;
11、 } //求差集。由属于第一个集合但不属于第二个集合的成员组成的新集合。 public Set Difference(Set aSet) { Set tempSet = new Set(); foreach (Object hashObject in data.Keys) { if (!(aSet.data.Contains(hashObject))) { tempSet.Add(data[hashObject]
12、); } } return tempSet; } public override string ToString() { string s = ""; foreach (Object key in data.Keys) s += data[key] + " "; return s; } } class Test { static void Main() { Set setA = n
13、ew Set(); setA.Add("中国"); setA.Add("日本"); setA.Add("美国"); setA.Add("韩国"); Set setB = new Set(); setB.Add("日本"); setB.Add("韩国"); setB.Add("加拿大"); Set setC = new Set(); setC = setA.Union(setB); Conso
14、le.WriteLine("A: " + setA.ToString()); Console.WriteLine("B: " + setB.ToString()); Console.WriteLine("A union B: " + setC.ToString()); setC = setA.Intersection(setB); Console.WriteLine("A intersect B: " + setC.ToString()); setC = setA.Difference(setB)
15、 Console.WriteLine("A diff B: " + setC.ToString()); setC = setB.Difference(setA); Console.WriteLine("B diff A: " + setC.ToString()); if (setB.Subset(setA)) { Console.WriteLine("B is a subset of A"); } else {
16、 Console.WriteLine("B is not a subset of A"); } Console.Read(); } } 运行结果如下: 2.1 泛型概述 泛型(Generic Type)是.NET Framework 2.0的一大特性。泛型的主要思想就是将算法与数据结构完全分离开来,使得一次定义的算法能够作用于多种数据结构,从而实现高度可重用的开发。通过泛型可以定义类型安全的数据结构,而没有必要使用实际的数据类型。这将显著提高性能并得到更高质量的代码,因为可以重用数据处理算法,而没有必要复制类
17、型特定的代码。 泛型是一个很强大的特性,对于集合类而言尤其如此。.NET 1.1中的大多数集合类都基于Object类型。.NET 从2.0开始提供了实现为泛型的新集合类。 泛型不仅限于类,还可用于委托、接口和方法的泛型。 在.NET 2.0之前,不存在泛型,在开发通用容器时,需要通用容器能存储各种类型的实例。在.NET Framework 1.1下,必须使用基于object类型的系统集合类(例如ArrayList,Stack,Queen等),但由于集合类中的项都是Object类型的,因此每次使用时都必须进行装箱拆箱操作,不仅大大降低了程序的性能,而且存在类型安全问题(Object类在编译
18、期间没有类型安全性)。
泛型(Generic Type)的出现较好的解决了上述问题,通过它,可以轻松创建指定类型的集合。泛型集合类不仅是类型安全的,而且使用时不需要进行烦琐的装箱拆箱操作。
泛型是C# 2.0中的新增元素(类似于C++中模板)。这种机制允许将类名作为参数传递给泛型类型,并生成相应的对象。主要利用System.Collections.Generic命名空间下面的List泛型类来创建集合。语法如下:
List
19、 下面看一个具体例子。 using System; using System.Collections.Generic; struct Student { public string sno; //学号 public string sname; //姓名 public int sage; //年龄 public Student(string sno, string sname, int sage) { this.sno = sno; this.sname = sname;
20、 this.sage = sage; } } class SamplesGeneric { static void Main() { //创建Student对象 Student stu1 = new Student("001", "张三", 30); Student stu2 = new Student("002", "李四", 20); Student stu3 = new Student("003", "王五", 50); //创建类型为Student的对
21、象集合
List
22、点是性能。对值类型使用System.Collections命名空间的集合类,存储数据时需要把值类型转换为引用类型,获取数据时需要把引用类型转换为值类型,即需要进行装箱和拆箱操作。例如,下面例子在存取数据时就进行了装箱拆箱操作。 ArrayList list = new ArrayList(); list.Add(44); // boxing - convert a value type to a reference type int i1 = (int)list[0]; // unboxing - convert a reference type to a value type
23、
foreach (int i2 in list)
{
Console.WriteLine(i2); // unboxing
}
装箱和拆箱操作很容易使用,但性能损失比较大,迭代许多项时尤其如此。
System.Collections.Generic命名空间中的List
24、alue types are stored in the List
25、ist.Add(44);
list.Add("mystring");
list.Add(new MyClass());
如果这个集合使用下面的foreach语句迭代,而该foreach语句使用整数元素来迭代,编译器就会编译这段代码。但并不是集合中的所有元素都可以转换为int,所以会出现一个运行异常:
foreach (int i in list)
{
Console.WriteLine(i);
}
错误应尽早发现。在泛型类List
26、)方法的参数无效:
List
27、ist
28、全,更高的效率,不过在约束方面,它只支持显式的约束,这样在灵活性方面就显得不是那么好了。泛型之所以能够提供更高的效率是因为泛型在实例化的时候采用了"on-demand"的模式,即按需实例化,发生在JIT(Just In Time)编译时。
下面来看如何定义一个泛型类,很简单,你只需要意识到一点——类型在这里被参数化了:
using System;
using System.Collections;
using System.Collections.Generic;
///
29、
30、l;
public Item
31、 newItem;
this.Last = newItem;
}
}
public IEnumerator
32、IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class SamplesGeneric
{
static void Main()
{
//使用string来实例化Collection
33、 names.Add("李四");
names.Add("王五");
foreach (string name in names)
{
Console.WriteLine(name);
}
//同样,也可以使用int来实例化Collection
34、 ages.Add(34);
ages.Add(47);
foreach (int age in ages)
{
Console.WriteLine(age);
}
Console.Read();
}
}
使用泛型类Collection
35、tion
36、实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。
using System;
using System.Collections;
using System.Collections.Generic;
class SamplesGeneric
37、0;如果T是引用类型则default(T)=null;
}
}
class Test
{
static void Main()
{
SamplesGeneric
38、Value); Console.Read(); } } 注意: default关键字根据上下文可以有多种含义。switch语句使用default定义默认情况。在泛型中,根据泛型类型是引用类型还是值类型,default关键字用于将泛型类型初始化为null或0。 2. 约束 C#中的泛型只支持显式的约束,因为这样才能保证C#所要求的类型安全,但显式的约束并非时必须的,如果不加约束,泛型类型参数将只能访问System.Object类型中的公有方法。“显式约束”由where子句表达,可以指定“基类约束”,“接口约束”,“构造器约束”,“值类型/引用类型约
39、束”共四种约束。
1、基类约束:
class A { public void F1() {} }
class B { public void F2() {} }
class C
where S: A // S继承自A
where T: B // T继承自B
{
// 可以在类型为S的变量上调用F1,
// 可以在类型为T的变量上调用F2
}
2、接口约束
interface IPrintable { void Print(); }
interface IComparable
40、terface IKeyProvider
41、)
{
//可以在其中使用T t=new T();
}
C c=new C(); //可以,A有无参构造器
C c=new C(); //错误,B没有无参构造器
4、值/引用类型约束
public struct A { }
public class B { }
class C
42、ere子句的一个重要限制是,不能定义必须由泛型类型执行的运算符。运算符不能在接口中定义。在where子句中,只能定义基类、接口和默认构造函数。
3. 继承
如何实现泛型类的继承呢?这里需要满足下面两点中的任何一点即可:
1、泛型类继承中,父类的类型参数已被实例化,这种情况下子类不一定必须是泛型类;
2、父类的类型参数没有被实例化,但来源于子类,也就是说父类和子类都是泛型类,并且二者有相同的类型参数;
//如果这样写的话,显然会报找不到类型T的错误
class SubItem : Item 43、ing>{ }
class SubItem 44、ric 45、ns.Generic;
interface IAdder 46、
class SamplesGeneric
{
static void Main()
{
Adder1 adder1 = new Adder1();
int sum = adder1.Add(5, 7);
Console.WriteLine(sum);
Adder2 adder2 = new Adder2();
string str = adder2.Add("hello", "world");
Console.WriteLine(str);
47、 Console.Read();
}
}
2.5 泛型方法
我们再来看泛型方法,C#的泛型机制只支持在方法声明上包含类型参数,也即是泛型方法。特别注意的是,泛型不支持在除了方法以外的其他类/接口成员上使用类型参数,但这些成员可以被包含在泛型类型中,并且可以使用泛型类型的类型参数。还有一点需要说的就是,泛型方法可以在泛型类型中,也可以存在于非泛型类型中。下面通过例子看一下泛型方法的声明与调用。
using System;
using System.Collections.Generic;
class Exchange
{
//声明一个泛型方法
48、 public static void Swap 49、 i, ref j);
Console.WriteLine("i = {0}, j = {1}", i, j);
//泛型方法可以像非泛型方法那样调用
Exchange.Swap(ref i, ref j);
Console.WriteLine("i = {0}, j = {1}", i, j);
Console.Read();
}
}
2.6 泛型委托
再来看一下泛型委托,泛型委托支持在返回值和参数上应用类型参数,下面例子中定义了一个类型参数为T的委托,然后在类中利用委托调 50、用方法:
using System;
using System.Collections.Generic;
delegate string GetString






