19.3 自定义集合
如果需要一个特定的集合类型,BCL中提供的集合类型无法满足需要时,例如要求一种集合类型只能添加某种数据类型到集合中,而不允许添加其他的类型;或者需要在元素添加或删除时进行某种额外的验证等,此时就需要自己来定义一个新的集合类型。不要把这件事想象的很难,事实上要自定义一个集合类型非常容易,在BCL中有抽象集合基类,我们只需要从这些基类继承,并添加一些定制的特性,即可设计出自定义的集合类型。
BCL中可供使用的抽象集合基类有两个。
❑CollectionBase:从该基类可以派生出集合元素可修改的强类型自定义集合;
❑ReadOnlyCollectionBase:此基类为CollectionBase的只读版本,可由它派生出非泛型的自定义集合,且该集合是只读的。
关于这两个抽象基类的用法,我们将在稍后分别介绍。
19.3.1 CollectionBase
继承并扩展CollectionBase可以创建一个可读写的集合类型。在开始之前,先对该类进行简单的介绍。CollectionBase类中提供了两个只读属性:InnerList和List,前者是ArrayList类型,用于存放集合的数据元素;后者是返回自定义集合类型的实例自身的引用,如下所示:
protected ArrayList InnerList
{
get
{
if(this.list==null)
{
this.list=new ArrayList();
}
return this.list;
}
}
protected IList List
{
get
{
return this;
}
}
注意这两个属性的访问修饰符:protected,意味着只有它自己和它的派生类能够访问。
应使用List来获取基类中的集合,而不是直接使用InnerList,原因是CollectionBase类提供了一些集合操作的方法,例如Add、Insert、Remove等,这些方法内部操作的就是InnerList集合,只不过在操作InnerList集合的前后都有一些事件方法可用。这里以Add方法为例,源代码如下:
//Add方法
int IList.Add(object value)
{
this.OnValidate(value);
this.OnInsert(this.InnerList.Count,value);
int index=this.InnerList.Add(value);
try
{
this.OnInsertComplete(index,value);
}
catch
{
this.InnerList.RemoveAt(index);
throw;
}
return index;
}
从上述代码可以看到,在进行真正的集合(InnerList)操作的前、后都有一些事件方法,例如OnValidate、OnClear、OnInsert、OnInsertComplete等。这些事件方法是虚方法,我们可以在自定义集合中根据需要重写它们,可以在这些事件方法中执行一些前置检查,或者结果检查。需要注意的是,尽量不要自己调用这些事件方法,除非你明确地想触发这些事件。这里以OnValidate方法为例,源码如下:
protected virtual void OnValidate(object value)
{
if(value==null)
{
throw new ArgumentNullException("value");
}
}
可见,此事件方法可以在数据加入集合前执行一些验证的操作。基类提供了丰富的事件方法,表19-11中列出了所有这些事件方法,并包括说明。
现在给出一个较完整的示例,在代码清单19-14中,定义一个名为Students的集合,它派生自CollectionBase基类。该集合提供了Add、Insert、IndexOf、Remove等方法,并且重写了几个事件方法。
代码清单19-14 从CollectionBase派生自定义集合代码示例
using System;
using System.Collections;
namespace ProgrammingCSharp4
{
class CollectionSample
{
public static void Main()
{
try
{
Students students=new Students();
students.Add(new Student{Name="Tom",Sex="Male"});
students.Add(new Student{Name="Alice",Sex="Female"});
students.Add(new Student{Name="Jack",Sex="Male"});
PrintCollectionInfo(students,"students");
students.Insert(1,new Student{Name="Bill",Sex="Male"});
PrintCollectionInfo(students,"students");
students.RemoveAt(2);
PrintCollectionInfo(students,"students");
students.Add("other");
}
catch(Exception e)
{
Console.WriteLine(“异常:”);
Console.WriteLine("-".PadLeft(50,'-'));
Console.WriteLine(e.Message);
}
}
public class Students:CollectionBase
{
public Students()
:base()
{
}
public void Add(Object student)
{
List.Add(student);
}
public void Insert(int index,Object student)
{
List.Insert(index,student);
}
public void Remove(Object student)
{
List.Remove(student);
}
public int IndexOf(Object student)
{
return List.IndexOf(student);
}
public bool Contains(Object student)
{
return List.Contains(student);
}
public Object this[int index]
{
get
{
return List[index];
}
}
protected override void OnInsert(int index,Object student)
{
Console.WriteLine(“在索引{0}插入了{1}”,index,(student as Student).Name);
}
protected override void OnRemove(int index,Object student)
{
Console.WriteLine(“在索引{0}移除了{1}”,index,(student as Student).Name);
}
protected override void OnSet(int index,Object oldValue,Object newValue)
{
Console.WriteLine(“在索引{0}将值{1}修改为{2}”,index,
(oldValue as Student).Name,(newValue as Student).Name);
}
protected override void OnValidate(Object student)
{
if(student.GetType()!=typeof(Student))
throw new ArgumentException(“集合的元素类型只能为Student类型.”,"student");
}
}
public class Student
{
public string Name{get;set;}
public string Sex{get;set;}
public string Grade{get;set;}
public DateTime Birthday{get;set;}
}
private static void PrintCollectionInfo(Students stuList,string varName)
{
Console.Write("{0}:",varName);
foreach(Student student in stuList)
{
Console.Write("\t{0}",student.Name);
}
Console.WriteLine();
Console.WriteLine("Count:{0}",stuList.Count);
}
}
}
上述代码的运行结果为:
在索引0插入了Tom
在索引1插入了Alice
在索引2插入了Jack
students:Tom Alice Jack
Count:3
在索引1插入了Bill
students:Tom Bill Alice Jack
Count:4
在索引2移除了Alice
students:Tom Bill Jack
Count:3
异常:
集合的元素类型只能为Student类型.
参数名:student
请按任意键继续……