標籤:
建議23:避免將List<T>作為自訂集合類的基類
如果要實現一個自訂的集合類,不應該以一個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!
轉自:《編寫高品質代碼改善C#程式的157個建議》陸敏技
編寫高品質代碼改善C#程式的157個建議——建議23:避免將List<T>作為自訂集合類的基類