如果要实现一个自定义的集合类,不应该以一个FCL集合类为基类,而应该扩展相应的泛型接口。FCL集合类应该以组合的形式包含至自定义的集合类,需扩展的泛型接口通常是IEnumerable<T>和ICollection<T>(或ICollection<T>的子接口,如IList<T>),前者规范了集合类的迭代功能,后者则规范了一个集合通常会有的操作。
在一般情况下,下面两个实现的集合类都能完成默认的需求:
class Employees1:List<Employee>
class Employees2:IEnumerable<Employee>,ICollection<Employee>
不过,遗憾的是,List<T>基本上没有提供可供子类使用的protected成员(从object中继承来的Finalize方法和MemberwiseClone方法除外),也就是说,实际上,继承List<T>并没有带来任何继承上的优势,反而丧失了面向接口编程带来的灵活性。而且,稍加不注意,隐含的Bug就会接踵而至。
以Employees1为例,如果要在Add方法中加入某些需求方面的变化,比如,这个变化是为名字添加一个后缀"Changed",但是客户端的开发人员也许已经习惯了面向接口的编程方式,他在为集合添加一个元素的时候使用了如下的语法:
static void Main(string[]args)
{
Employees1 employees1=new Employees1()
{
new Employee(){Name="Mike"},
new Employee(){Name="Rose"}
};
IList<Employee>employees=employees1;
employees.Add(new Employee(){Name="Steve"});
foreach(var item in employees1)
{
Console.WriteLine(item.Name);
}
}
class Employee
{
public string Name{get;set;}
}
class Employees1:List<Employee>
{
public new void Add(Employee item)
{
item.Name+="Changed!";
base.Add(item);
}
}
于是,代码的实际输出会偏离集合类的设计者的设想。以上代码的输出为:
Mike Changed!
Rose Changed!
Steve
要纠正这类非预期的行为,我们应该采用Employees2的方式:
static void Main(string[]args)
{
Employees2 employees2=new Employees2()
{
new Employee(){Name="Mike"},
new Employee(){Name="Rose"}
};
ICollection<Employee>employees=employees2;
employees.Add(new Employee(){Name="Steve"});
foreach(var item in employees2)
{
Console.WriteLine(item.Name);
}
}
class Employees2:IEnumerable<Employee>,ICollection<Employee>
{
List<Employee>items=new List<Employee>();
#region IEnumerable<Employee>成员
public IEnumerator<Employee>GetEnumerator()
{
return items.GetEnumerator();
}
#endregion
#region ICollection<Employee>成员
public void Add(Employee item)
{
item.Name+="Changed!";
items.Add(item);
}
//省略
#endregion
}
以上代码的输出为:
Mike Changed!
Rose Changed!
Steve Changed!