第五章 泛型和集合类
微软在2005年底正式发布了C#2.0版本,与C#1.0版本相比,新版本增加了许多新功能,其中最重要的是对泛型的支持。通过泛型,我们可以定义类型更安全的数据结构,而无需使用实际的数据类型,或者不可靠的装箱和拆箱操作。这样显著提高代码的性能,同时也提高了代码的质量。泛型,其实不是什么新鲜的东西,它在功能上有点类似于C++的模板,但是又和C++的模板有原理上的差别。
本章讨论使用泛型的常见问题,比如说为什么要使用泛型、怎样编写泛型方法、泛型约束的使用、泛型方法的重载问题等,通过这些使我们可以大致了解泛型并掌握泛型的常规应用,编写出更简单、通用、高效的代码。同时,我们还讨论了几种常用集合类的应用。
本章的学习重点:
◆ 集合类概念
◆ 集合类的共性
◆ Stack 类
◆ Queue类
◆ Hashtable类
◆ SortedList类
◆ ArrayList类
◆ NameValueCollection类
5.2集合类
集合(集合类)就好比是一个袋子,可以装下任何的东西。我们当然不希望用这个袋子又装好吃的又要装垃圾。所以,针对.NET Framework的2.0版本和更高版本的应用程序应当使用System.Collections.Generic命名空间中的泛型集合类,与对应的非泛型集合类相比,这些类提供了更高的类型安全性和效率,这样的话,以前的非泛型集合类建议停止使用。本文将向大家重点介绍泛型集合类的定义和使用方法等。
5.2.1集合类概念
.NET Framework提供了用于存储和检索数据的专用类,这就是集合类。这些类提供对堆栈、队列、列表和哈希表等的支持。大多数集合类实现相同的接口,可继承这些接口来创建适应性更为专业的数据存储集合类。
集合类具有以下特点:
1、集合类定义为System.Collections或System.Collections.Generic命名空间的一部分。
2、大多数集合类都派生自ICollection、IComparer、IEnumerable、IList、IDictionary和IDictionaryEnumerator接口以及它们的等效泛型接口。
3、使用泛型集合类可以提供更高的类型安全性,在某些情况下还可以提供更好的性能,尤其是在存储值类型时,这些优势会体现得更明显。
下面是常用的非泛型集合类对应的泛型集合类:
非泛型集合类 泛型集合类
ArrayList List<T>
HashTable Dictionary<T>
Queue Queue<T>
Stack Stack<T>
SortedList SortedList<T>
很多非泛型集合类都有对应的泛型集合类,我们用的比较多的非泛型集合类主要有ArrayList类和 HashTable类。我们经常用HashTable 来存储将要写入到数据库或者返回的信息,在这之间要不断的进行类型转化,增加了系统装箱和拆箱的负担。如果我们操纵的数据类型相对确定的话用Dictionary<TKey,TValue>集合类来存储数据就方便多了。
数据结构就是把多个相同类型的对象组合起来,最简单的数据结构就是数组。我们可以通过IEnumerable或者ICollection接口来给对象添加集合的支持,实现高效复杂的数据操作功能。
因为任何集合类都实现了IEnumerable接口,所以任何集合类对象都有一个GetEnumerator()方法,该方法可以返回一个实现了IEnumerator接口的对象,这个返回的IEnumerator对象既不是集合类对象,也不是集合的元素类对象,它是一个独立的类对象。通过这个对象,可以遍历访问集合类对象中的每一个元素对象。如果集合类是用户自定义的集合类,则用户必须实现它的GetEnumerator()方法,否则不能使用循环。
下面让我们逐个向大家介绍这些接口和类:
一、IEnumerable和IEnumerator接口
IEnumberable接口是所有集合类的根。继承这个接口就支持foreach循环,一个集合对象要能遍历所有对象的话,那么必需要实现这个接口,它的声明格式如下:
public interface IEnumerable
{
IEnumerator GetEnumerator ();
}
这里的GetEnumerator方法返回的还是一个接口IEnumerator,可以用这个返回的IEnumerator类型接口实现对集合对象的遍历。
IEnumerator接口的声明格式如下:
public interface IEnumerator
{
Object Current { get; } //获取集合的当前元素
bool MoveNext (); //枚举成功推进到下一个元素,为true;否则为false
void Reset () ; //设置枚举数为其初始位置,也就是第一个元素的前面。
}
二、ICollection接口
ICollection接口派生自IEnumerable接口,扩展了集合类元素个数和同步功能,即对多线程安全访问的支持。
ICollection接口的声明格式如下:
public interface ICollection : IEnumerable
{
int Count { get; } //获取集合中的元素个数
bool IsSynchronized { get; } //获取是否同步访问的值。
Object SyncRoot { get; } //获取用于同步访问的对象。
}
所有实现ICollection接口的对象,可以在对集合元素遍历的时候,把集合锁定,以防其它线程对集合的修改。ICollection接口派生了IDictionary和IList两个子接口。
三、IList接口
IList接口派生自ICollection,扩展了集合类的索引功能,可以通过索引来访问其成员。例如:ArrayList集合实现了IList接口,使ArrayList可以通过索引来访问集合的成员。
IList接口的声明格式如下:
public interface IList : ICollection, IEnumerable
{
int Add(object value); //把某项添加到IList集合中
void Clear (); //从IList中移除所有项
bool Contains (Object value); //检查IList是否包含某项
int IndexOf (Object value); //获取IList集合中某项的索引
void Insert (int index,Object value); //把某项插入IList集合的指定位置
void Remove(object value); //从IList集合中移除某项
void RemoveAt(int index); //从IList集合中移除指定索引的项
bool IsFixedSize { get; } //获取IList集合是否是固定大小
bool IsReadOnly { get; } //获取IList集合是否是只读
Object this [int index] { get; set; } //获取或设置指定索引的内容
}
四、IDictionary接口
IDictionary接口是实现一个“键/值”对的集合,派生自ICollection接口,可以用循环遍历其中的每一个元素。其中的每个“键/值”对是一个DictionaryEntry对象,只能用键名来存取对象,不能用索引号来存取对象。如HashTable
IDictionary接口的声明格式如下:
public interface IDictionary : ICollection, IEnumerable
{
void Add (Object key,Object value); //在IDictionary集合中添加键和值对元素
void Clear (); //从IDictionary中移除所有项
bool Contains (Object value); //检查IDictionary是否包含某项
void Remove (Object key); //从IDictionary集合中移除指定键值的项
bool IsFixedSize { get; } //获取IDictionary是否是固定大小
bool IsReadOnly { get; } //获取IDictionary是否是只读
Object this [Object key] { get; set; } //获取或设置具有指定键的元素
ICollection Keys { get; } //获取IDictionary中的所有的键集合
ICollection Values { get; } //获取IDictionary中的所有的值集合
}
五、ArrayList类
ArrayList类实现了IList接口,用来存储非泛型对象。这个集合类可以通过索引来访问成员。
ArrayList类的声明格式如下:
public class ArrayList : Ilist, ICollection, IEnumerable
{
int Count { get } //获取集合中实际元素的个数
int Capacity{ get; set; } //获取或者设置ArrayList容器空间的大小,默认为4
void AddRange (ICollection c) //将一个新的ICollection对象加到当前ArrayList后面
ArrayList GetRange (int index,int count) //从ArrayList对象中返回指定位置和指定数目的元素
void InsertRange(int index,ICollection c) //在指定的位置插入另一个ICollection对象
void TrimToSize() //将ArrayList的容量减到ArrayList的实际的元素个数
void Reverse() //将数组的顺序进行返转
void Sort() //将数组进行排序
}
六、Hashtable类
Hashtable类实现了IDictionary接口,可以用来存储非泛型对象。由于Hashtable实现的是IEnumerable接口,所以可以用foreach对Hashtable进行遍历。
Hashtable类的声明格式如下:
public class Hashtable : IDictionary
{
void ContainsKey(object o) //判断Hashtable是否包含指定键
void ContainsValue(object o) //判断Hashtable是否包含指定值
Hashtable Synchronized (Hashtable t) //将一个普通的Hashtable包装为线程安全Hashtable
}
七、SortedList类:
SortedList与Hashtable一样也是一个“键/值”对的集合,但它是按照关键字进行排序的,其值是可以通过数字索引来处理。
SortedList类的声明格式如下:
public class SortedList: IDictionary
{
object GetKey(int index): //根据索引号来取得指定位置的键
object GetByIndex(int index): //根据索引号来取得指定位置的值
IList GetKeyList(): //获取SortedList中的键的集合
IList GetValueList(): //获取SortedList中的值的集合
object IndexOfKey(object var): //取出SortedList中指定键的索引号
object IndexOfValue(object var): //取出SortedList中指定值的索引号
}
八、Queue类和Stack类
Queue类和Stack类都实现了ICollection接口和IEnumerable接口,但是没有实现IList接口。Queue是一个队列,就像我们排队一样,先入队的元素,可以先出队。出队和入队都分影响Queue对象的元素的个数,即影响其Count属性。规则简称:先进先出
Queue类的声明格式如下:
public class Queue: ICollection, IEnumerable
{
Enqueue(object var) //进行入队操作
object Dequeue() //进行出队操作
int Count { get; } //获取Queue中的元素的集合
}
Stack是一个堆栈,模仿内存堆栈进出数据的规则,先进栈的数据被压在下面,比较晚才出栈,后进栈的数据,在堆栈的上面,可以先出栈。入栈和出栈都会影响Stack的元素个数。规则简称:先进后出。
Stack类的声明格式如下:
public class Stack: ICollection, IEnumerable
{
Push(object var) //进行进栈操作
object Pop() //进行出栈操作
int Count { get; } //获取Stack中的元素的集合
}
5.2.2集合类的共性
泛型集合(如List)和强类型非泛型集合(如StringCollection)可避免造成性能损耗严重的问题,前提是元素的类型是集合所需的类型(例如,从StringCollection存储或检索字符串)。此外,强类型集合自动执行添加到该集合的每一元素的类型验证。
直接或间接实现ICollection接口或者ICollection泛型接口的所有集合,除了有添加、移除或搜索元素的功能外,还有下面几个公共功能:
1、 枚举数
枚举数是循环访问其关联集合的对象。它可被视作指向集合中任何元素的可移动的指针。一个枚举数只能与一个集合关联,但一个集合可以具有多个枚举数。C# foreach 语句使用的也是枚举数,但隐藏操作该枚举数的复杂性。
2、同步
同步是在访问集合的元素时提供线程安全。默认情况下集合不是线程安全的。在System.Collections 命名空间中只有几个类提供Synchronize方法,该方法能够超越集合创建线程安全包装。但是,System.Collections命名空间中的所有类都提供SyncRoot属性,可供派生类创建自己的线程安全包装。还提供了IsSynchronized属性以确定集合是否是线程安全的。ICollection泛型接口中不提供同步功能。
3、CopyTo方法
使用CopyTo方法可将所有集合复制到数组中;但是,新数组中元素的顺序基于枚举数返回它们的顺序。结果数组始终是具有零下限的一维数组。
4、容量和计数
集合的容量是其可以包含的元素的数目。集合的计数是其实际包含的元素的数目。BitArray是一个特例;其容量与其长度相同,而其长度与其计数相同。某些集合隐藏容量或计数(或两者一起隐藏)。System.Collections命名空间中的所有集合在达到当前容量时可自动扩充容量。内存被重新分配,元素从旧集合复制到新集合中。这减少了使用集合所需的代码;但是,集合的性能可能仍受到消极影响。避免因多次重新分配导致不佳性能的最佳方法是将初始容量设置为集合的估计的大小。
5、下限
集合的下限是其第一个元素的索引。System.Collections命名空间中的所有索引集合的下限均为零。Array的下限在默认情况下为零,但使用CreateInstance创建Array类的实例时可以定义不同的下限。
System.Collections 类通常可以分为三种类型:
1、 常用集合
这些集合是数据集合的常见变体,如哈希表、队列、堆栈、字典和列表。常用集合有泛型和非泛型之分。
2、 位集合
这些集合中的元素均为位标志。它们的行为与其他集合稍有不同。
3、 专用集合
这些集合都具有专门的用途,通常用于处理特定的元素类型,如StringDictionary。
最后,选择集合类时务必要小心。因为每一集合都有其自身的功能,因此每一集合也就具有其自身的限制。集合的专用性越强,其限制也就越多。
5.2.3 Queue类
队列(Queue)在程序设计中扮演着重要的角色,因为它可以模拟队列的数据操作。例如,排队买票就是一个队列操作,新来的人排在后面,先来的人排在前面,并且买票请求先被处理。为了模拟队列的操作,Queue在ArrayList的基础上加入了以下限制:
1、元素采用先入先出机制(FIFO,First In First Out),即先进入队列的元素必须先离开队列。最先进入的元素称为队头元素。
2、元素只能被添加到队尾(称为入队),不允许在中间的某个位置插入。也就是说,不支持ArrayList中的Insert方法。
3、只有队头的元素才能被删除(称为出队),不允许直接对队列中的非队头元素进行删除,从而保证FIFO机制。也就是说,不支持ArrayList中的Remove方法。
4、不允许直接对队列中非队头元素进行访问。也就是说,不支持ArrayList中的索引访问,只允许遍历访问。
下面我们通过一个示例向大家演示如何创建Queue对象。
using System;
using System.Collections;
public class TestQueue
{
public static void PrintQueue(Queue queue)
{
Console.WriteLine("遍历队列内容:");
foreach (string value in queue) //遍历队列内容
{
Console.WriteLine(value);
}
}
}
public class Program
{
public static void Main(string[] args)
{
Queue queue = new Queue(); //声明一个队列
Console.WriteLine("请输入字符串添加到队列:(#号结束)");
string value = string.Empty;
do
{
value = Console.ReadLine();
if (value == "#")
{
break;
}
else
{
queue.Enqueue(value); //入队操作
}
} while (true);
Console.WriteLine("开始遍历队列内容");
TestQueue.PrintQueue(queue);
Console.WriteLine("结束遍历队列内容");
Console.WriteLine();
Console.WriteLine("开始出队");
while (queue.Count > 0)
{
value = (string)queue.Dequeue(); //出队操作
Console.WriteLine(value + " 已经出队,并且被删除!");
}
Console.WriteLine("结束出队");
}
}
上述代码中,我们在Main方法中,如下语句构造了一个Queue对象:Queue queue = new Queue();
然后要求用户连续输入若干行字符串,一直到用户输入“#”为止就结束输入。
对于用户每个输入的字符串,使用以下语句将字符串添加到Queue对象queue的末尾:queue.Enqueue(value);。输入结束后,使用PrintQueue方法遍历并打印queue中的数据内容,注意,这里并没有删除数据,只是遍历读取。
最后,使用如下语句从queue中逐一出队元素,并将该元素输出到控制台,这里每出队一个元素,就会删除一个元素,直到队列中元素为空:value = (string)queue.Dequeue();
Queue.Enqueue (object):向Queue中入队一个对象元素。
Queue.Dequeue ():从Queue中出队一个对象元素,并将此元素返回。
输出结果:
请输入字符串添加到队列:(#号结束)
6
7
8
#
开始遍历队列内容
遍历队列内容:
6
7
8
结束遍历队列内容
开始出队
6 已经出队,并且被删除!
7 已经出队,并且被删除!
8 已经出队,并且被删除!
结束出队
5.2.4 Stack类
Stack类是一个后进先出的数据结构,这个类定义了两个重要的方法,一个是Push压栈方法,另外一个是Pop出栈方法。Push方法将一个对象保存到Stack的最顶端,而Pop方法则从Stack里将最顶端的对象取出,并且将其删除。还有一个方法Peek(),这个方法只是取得对象的值并不会删除对象的值。
下面我们通过一个示例向大家演示如何创建Stack对象。
using System;
using System.Collections;
public class TestStack
{
public static void PrintStack(Stack stack)
{
Console.WriteLine("遍历堆栈内容:");
foreach (string value in stack) //遍历堆栈内容
{
Console.WriteLine(value);
}
}
}
public class Program
{
public static void Main(string[] args)
{
Stack stack = new Stack(); //声明一个堆栈
Console.WriteLine("请输入字符串添加到堆栈:(#号结束)");
string value = string.Empty;
do
{
value = Console.ReadLine();
if (value == "#")
{
break;
}
else
{
stack.Push(value); //压栈操作
}
} while (true);
Console.WriteLine("开始遍历堆栈内容");
TestStack.PrintStack(stack);
Console.WriteLine("结束遍历堆栈内容");
Console.WriteLine();
Console.WriteLine("开始出栈");
while (stack.Count > 0)
{
value = (string)stack.Pop(); //出栈操作
Console.WriteLine(value + " 已经出栈,并且被删除!");
}
Console.WriteLine("结束出栈");
}
}
上述代码中,我们在第18行位置的Main方法中,构造了一个Stack对象:Stack stack = new Stack();
然后要求用户连续输入若干行字符串,一直到用户输入“#”为止就结束输入,这个和上面范例一样。
对于用户每个输入的字符串,使用以下语句将字符串添加到Stack对象stack的顶部:stack.Push(value);。输入结束后,使用PrintStack方法遍历并打印stack中的数据内容,注意,这里并没有删除数据,只是遍历读取。
最后,我们在第40行使用了Pop语句从stack中逐一出栈元素,并将该元素输出到控制台,这里每出栈一个元素,就会删除一个元素,直到堆栈中元素为空:value = (string)stack.Pop();
stack. Push (object):向stack中压栈一个对象元素。
stack. Pop ():从stack中出栈一个对象元素,并将此元素返回。
运行结果:
请输入字符串添加到堆栈:(#号结束)
7
8
9
#
开始遍历堆栈内容
遍历堆栈内容:
9
8
7
结束遍历堆栈内容
开始出栈
9 已经出栈,并且被删除!
8 已经出栈,并且被删除!
7 已经出栈,并且被删除!
结束出栈
5.2.5 Hashtable类
哈希,即“键/值”对。类似于字典(比数组更强大)。哈希是经过优化的,访问下标的对象先散列过。Object对象的GetHashCode()方法返回一个int类型数据,就是用键生成的int类型数据,表示数据项在字典中存储的位置。
HashTable中的key/value是object类型,HashTable的优点就在于其索引的方式,速度非常快。如果以任意类型键值访问其中元素会快于其他集合,特别是当数据量特别大的时候,效率差别尤其大。
HashTable的应用场合有:做对象缓存,树递归算法的替代,和各种需提升效率的场合。
下面我们通过一个示例向大家演示如何创建Hashtable对象。
using System;
using System.Collections;
namespace Microsoft.HashTable
{
public class Program
{
public static void Main(string[] args)
{
Hashtable hash = new Hashtable(); //定义一个“哈希”对象
for (int i = 0; i < 5; i++) //数据准备
{
string key = "key" + i;
string value = "value" + i;
hash.Add(key, value);
}
Console.WriteLine("遍历哈希的键:");
ICollection keys = hash.Keys;
foreach (string key in keys)
{
Console.WriteLine(key);
}
Console.WriteLine("遍历哈希的值:");
ICollection values = hash.Values;
foreach (string value in values)
{
Console.WriteLine(value);
}
Console.WriteLine("用“字典枚举器”遍历哈希:");
IDictionaryEnumerator ide = hash.GetEnumerator();
while (ide.MoveNext())
{
Console.WriteLine("key:{0},value:{1}", ide.Key, ide.Value);
}
Console.WriteLine("用“字典条目”遍历哈希:");
foreach (DictionaryEntry dic in hash)
{
Console.WriteLine("key:{0},value:{1}", dic.Key, dic.Value);
}
Console.WriteLine();
do
{
Console.WriteLine("请输入哈希的键:");
string key = Console.ReadLine();
if (hash.ContainsKey(key))
{
Console.WriteLine("key:{0},value:{1}", key, hash[key]);
}
else
{
Console.WriteLine("错误,请重新输入哈希的键:");
}
} while (true);
}
}
}
上述代码中,第9行,我们定义了一个hash对象,并且向对象中添加了5项作为数据准备,这里使用hash.Add方法来添加数据,key是键的值,value是键包含的值。
这里我们使用了多种方法来遍历Hashtable,比较有代表性意义的是,用“字典枚举器”遍历哈希和“字典条目”遍历哈希。“字典枚举器”用hash对象的GetEnumerator方法获取到可以用来枚举的对象IDictionaryEnumerator,然后执行ide.MoveNext,如果下面一个元素有数据,则返回True,然后取到这个键值对的数据出来。而“字典条目”的方法是直接遍历hash集合,因为hash集合是由一个一个DictionaryEntry组成的,所有,可以获取得到hash集合中具体项,然后根据具体项得到我们要的键值对。
输出结果:
遍历哈希的键:
key0
key2
key4
key1
key3
遍历哈希的值:
value0
value2
value4
value1
value3
用“字典枚举器”遍历哈希:
key:key0,value:value0
key:key2,value:value2
key:key4,value:value4
key:key1,value:value1
key:key3,value:value3
用“字典条目”遍历哈希:
key:key0,value:value0
key:key2,value:value2
key:key4,value:value4
key:key1,value:value1
key:key3,value:value3
HashTable是经过优化的,访问下标的对象先散列过,所以内部是无序散列的,保证了高效率,也就是说,其输出不是按照开始加入的顺序,而Dictionary遍历输出的顺序,就是加入的顺序,这点与Hashtable不同。如果一定要排序HashTable输出,只能自己实现:
System.Collections.ArrayList keys = new System.Collections.ArrayList(hash.Keys);
keys.Sort();
foreach (string key in keys)
{
Console.Write(key + ":");
Console.WriteLine(hash[key]);
}
为了保证在多线程的情况下的线程同步访问安全,微软提供了自动线程同步的HashTable,如果 HashTable要允许并发读但只能一个线程写,可以这么创建HashTable实例:
Hashtable htSyn = Hashtable.Synchronized(new Hashtable());
这样,如果有多个线程并发的企图写HashTable里面的item,则同一时刻只能有一个线程写,其余阻塞,对读的线程则不受影响。
另外一种方法就是使用lock语句,但要lock的不是HashTable,而是其SyncRoot;效果是一样的。
private static Hashtable hash = new Hashtable();
public static void AccessHashtable()
{
lock (hash.SyncRoot)
{
//Do something
}
}
5.2.6 SortedList类
SortedList与Hashtable类似,区别在于SortedList中的Key组已经排序了。
所以,这里就不复述它们的概念,代码也很简单,请看下面的演示:
using System;
using System.Collections;
public class TestSortedList
{
public static void Main(string[] args)
{
SortedList sortedList = new SortedList(); //定义一个SortedList对象
sortedList.Add("c", "apple");
sortedList.Add("a", "banana");
sortedList.Add("b", "tomato");
foreach (DictionaryEntry dic in sortedList) //遍历SortedList对象
{
Console.WriteLine("key:{0}, value:{1}", dic.Key, dic.Value);
}
}
}
上述代码中,第7行定义了一个SortedList对象,然后我们使用这个对象的Add方法想这个对象添加了三项数据,注意,这三项数据的键的顺序是打乱的。然后,我们使用DictionaryEntry字典条目的方法来遍历SortedList对象的数据,SortedList对象与Hashtable对象很类似,区别就是它们的Key不是根据我们的输入顺序来输出的,而是经过排序的,这个我们在输出结果就可以知道了。
输出结果:
key:a, value:banana
key:b, value:tomato
key:c, value:apple
5.2.7 ArrayList类
数组是System.Array的一个实例,Array可以具有多个维度,高效地访问给定下标(下限可自定)的元素,但数组有一个缺点就是实例时必须指定数组大小,实例后不能添加、插入或删除元素;而且,它只能通过数字下标访问元素,比如根据需要按照Employee的姓名查找时,这个下标就失去了在检索中的作用。所以,ArrayList出现了。ArrayList就是可以动态添加的数组,可以按照姓名查找的数组。
下面我们通过一个示例向大家演示如何创建ArrayList对象。
using System;
using System.Collections;
public class TestArrayList
{
public static void Main(string[] args)
{
ArrayList list = new ArrayList(5); //定义一个ArrayList对象
list.Add("1"); //添加一个数据
string[] items = { "2", "3", "4"}; //定义一个数组
list.AddRange(items); //添加一组数据
list[3] = "5"; //修改一个数据
Console.Write("ArrayList对象的大小是:");
Console.WriteLine(list.Count); //获取对象的个数
list.RemoveAt(2); //删除一个数据
list.Remove("1");
Console.Write("ArrayList对象的大小是:");
Console.WriteLine(list.Count); //获取对象的个数
foreach (object o in list) //遍历对象
{
if (o != null)
{
if (o is string)
{
Console.WriteLine(o.ToString()+ " 是 " + typeof(string).ToString() + " 类型");
}
else
{
Console.WriteLine(o.ToString());
}
}
}
}
}
上述代码中,我们在第7行代码中定义了一个ArrayList对象list,同时,我们在定义的时候,就指定它的初始容量是5,如果超过这个数,它的容量会成倍的增长,也就是10。第8行我们往list对象里添加了一个对象,使用Add方法。同时,我们可能会出现想第10行一样,一次添加一批数据,这样才方便嘛。
list对象可以很方便的删除数据,这里我们向大家演示了两种删除数据的方式。一种是第14行的,使用索引来删除数据。还有一种是第15行的,使用内容来删除数据。
第18行,我们使用foreach结构体来遍历list对象,list里面包含的是对象object。所以,这里我们做了一下防御性的编码。首先,判断list对象的项是否为Null,list是可以添加Null值的。然后,再进行类型判断,这里我们使用is关键字。如果是string类型,我们就把它的值输出来,否则,就输出类型的名称。
输出结果:
ArrayList对象的大小是:4
ArrayList对象的大小是:2
2 是 System.String 类型
5 是 System.String 类型
5.2.8 NameValueCollection类
NameValueCollection定义为特殊的集合类,在放在System.Collections.Specialized命名空间下。System.Collections.Specialized命名空间下还有HybridDicionary类,建议少于10个元素用HybridDicionary,当元素增加会自动转为HashTable。
NameValueCollection和HashTable很相似,但是它们还有一个区别,就是HashTable的key是唯一性,而NameValueCollection则不是唯一的,可以相同。
System.Collections.Specialized下还有其他类大家可以各取所需。
下面我们通过一个示例向大家演示如何创建NameValueCollection对象。
using System;
using System.Collections;
using System.Collections.Specialized;
public class TestNameValueCollection
{
public static void Main(string[] args)
{
Hashtable hash = new Hashtable(); //定义一个Hashtable对象
hash.Add("1", "X"); //添加数据到Hashtable对象中
hash.Add("2", "Y");
hash.Add("3", "Z");
foreach (object key in hash.Keys) //遍历Hashtable对象中的数据
{
Console.WriteLine("key:{0}; value{1}", key, hash[key]); //显示数据
}
Console.WriteLine();
NameValueCollection NVColl = new NameValueCollection();//定义NameValueCollection对象
NVColl.Add("1", "X"); //添加数据到NameValueCollection对象中
NVColl.Add("1", "X");
NVColl.Add("2", "Y");
NVColl.Add("3", "Z");
foreach (string key in NVColl.Keys) //遍历NameValueCollection对象中的23 数据
{
Console.WriteLine("key:{0}; value{1}", key, NVColl[key]); //显示26 数据
}
}
}
上述代码中,我们定义了两个对象,一个是HashTable对象,另外一个是NameValueCollection对象。这两个对象非常的相似。它们之间的不同点就是NameValueCollction可以添加相同Key的数据。在第18,19行,我们给NVColl对象添加了两条相同的数据,这些在HashTable中是做不到的。这两个的应用的都很广泛,以后要是遇到需要相同Key的“HashTable”,那么就用NamaValueCollection对象。
输出结果:
key:1; valueX
key:2; valueY
key:3; valueZ
key:1; valueX,X
key:2; valueY
key:3; valueZ