[C#.NET] 若要自訂義基底集合類別,應避免繼承List<T>
我相信或多或少都會使用到自訂義的集合類別,比如說,為了讓集合內的項目不重覆,Add時會先處理一些條件判斷式,才會真的加入到集合裡,來看一下程式碼。
public class CellPhone
{
public string CountryCode { get; set; }
public string Phone { get; set; }
private string _FullCellPhone;
public string FullCellPhone
{
get
{
_FullCellPhone = CountryCode + Phone;
return _FullCellPhone;
}
internal set
{
_FullCellPhone = value;
}
}
}
繼承了List<CellPhone> ,並用 new 關鍵字取代舊的 Add 方法,這樣的寫法或許常常發生在你我身邊
public class CellPhones : List<CellPhone>
{
public new void Add(CellPhone Item)
{
var query = from data in this
where data.Phone == Item.Phone
select data;
var result = query.FirstOrDefault();
if (result == null)
{
base.Add(Item);
}
}
}
寫個簡單的測試,確保集合內不會出現重複項目。
public void QueryTest2()
{
CellPhones phones = new CellPhones()
{
new CellPhone(){CountryCode="+86",Phone="338-2817"},
};
phones.Add(new CellPhone() { CountryCode = "+86", Phone = "338-2817" });
Assert.IsTrue(phones.Count == 1);
}
這樣看起來好像沒什麼太大的問題,可是他會帶來淺在的Bug,若有人已經很習慣用 Interface 實體化,既然集合類別是繼承 List<T>,當然可以使用 IList<T> 實體化。
當然這樣就不會通過測試了,IList<T>並不會呼叫新的 Add 方法
public void QueryTest3()
{
IList<CellPhone> phones = new CellPhones()
{
new CellPhone(){CountryCode="+86",Phone="338-2817"},
};
phones.Add(new CellPhone() { CountryCode = "+86", Phone = "338-2817" });
Assert.IsTrue(phones.Count == 1);
}
若要正確的實作一個集合類別,你可以跟我這樣做 實作IEnumerable<CellPhone>, ICollection<CellPhone> 兩個接口。
public class CellPhones : IEnumerable<CellPhone>, ICollection<CellPhone>
{
List<CellPhone> _items = new List<CellPhone>();
public int Count
{
get { return this._items.Count; }
}
public bool IsReadOnly
{
get
{
return false;
}
}
public void Add(CellPhone Item)
{
var query = from data in this
where data.Phone == Item.Phone
select data;
var result = query.FirstOrDefault();
if (result == null)
{
this._items.Add(Item);
}
}
public bool Remove(CellPhone Item)
{
return this._items.Remove(Item);
}
public void Clear()
{
this._items.Clear();
}
public bool Contains(CellPhone item)
{
return this._items.Contains(item);
}
public void CopyTo(CellPhone[] array, int arrayIndex)
{
this._items.CopyTo(array, arrayIndex);
}
public IEnumerator<CellPhone> GetEnumerator()
{
return this._items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
如此一來,用戶端或是其他設計師,使用Interface 實體化時,也能正確的呼叫我們所寫的 Add 方法
public void QueryTest4()
{
ICollection<CellPhone> phones = new CellPhones()
{
new CellPhone(){CountryCode="+86",Phone="338-2817"},
};
phones.Add(new CellPhone() { CountryCode = "+86", Phone = "338-2817" });
Assert.IsTrue(phones.Count == 1);
}
後記:
若你開發的是基底類別,由上面的說明可以得知,請乖乖的實作特定接口,例如文中所寫的的IEnumerable<CellPhone>, ICollection<CellPhone> 兩個接口,否則其他人不會知道你內部的實作過程,避免隱含 Bug 的發生。
若你真的只想繼承List<T>,請確定你寫的類別不會被其他人調用。
補充:
不僅是List<T>會有這樣的隱含Bug,我們在設計類別時應該要小心,我們不應該去繼承一個實作類別,因為實作類別裡已經繼承了多個接口(Interface)。
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET