在物件導向專案中,常需將業務實體 (如 Customer) 組織為集合 (Customers),並支援索引存取、列舉、強型別約束等功能。.NET System.Collections 命名空間提供多種基礎類別,本文對比兩種主流實作路徑:繼承 CollectionBase 與 封裝 ArrayList,並深入探討索引器、Item 屬性、屬性封裝、延遲初始化等關鍵技術。
public class Customer
{
public string Name { get; set; }
// public Contacts Contacts { get; set; } // 集合關聯稍後說明
public Customer() { Console.WriteLine("Initialize instance without parameter"); }
public Customer(string name) { Name = name; Console.WriteLine("Initialize instance with parameter"); }
}
public class Customers : CollectionBase
{
public void Add(Customer c) => List.Add(c);
public void Remove(int index)
{
if (index < 0 || index >= Count) Console.WriteLine("Index not valid!");
else List.RemoveAt(index);
}
// 索引器
public Customer this[int index]
{
get => (Customer)List[index];
// 刻意不提供 set,形成唯讀索引器
}
// Item 方法 (供 VB 風格呼叫)
public Customer Item(int index) => (Customer)List[index];
// Item 屬性 (回傳 this 以支援 customers.Item[i] 語法)
[IndexerName("Item")] // 指定編譯後索引器名稱避免衝突
public Customers Item => this;
}
Count、Clear、RemoveAt 等 IList 介面,僅需補充強型別 Add/Remove。Item 屬性,若同時定義 Item 方法/屬性會衝突。解決:在索引器前加 [IndexerName("item")] 指定別名。Item(int):效能佳,語法 custs.Item(i)。Item { get { return this; } }:支援 custs.Item[i],但多一次索引器呼叫,效能略低。Add(object),無法阻止加入非 Contact 物件,型別安全全失。public class Contacts
{
private ArrayList _list = new ArrayList();
public void Add(Contact c) => _list.Add(c);
public void Remove(int index) { /* 檢查索引 */ _list.RemoveAt(index); }
public int Count => _list.Count;
// 完整索引器 (含 set,允許整個元素替換)
public Contact this[int index]
{
get => (Contact)_list[index];
set => _list[index] = value;
}
}
public class Contact
{
private string _summary;
public string Summary
{
get { Console.WriteLine("getter Access"); return _summary; }
set { Console.WriteLine("setter Access"); _summary = value; }
}
}
contacts[0] = new Contact(); // 呼叫 set,替換整個元素
contacts[0].Summary = "New value"; // 呼叫 get 取得物件,再設其屬性,不觸發索引器 set
只有「替換集合元素本身」才會觸發 set;修改元素內部屬性則不會。依業務決定是否提供 set。
| 特性 | 欄位 | 屬性 |
|---|---|---|
| 驗證邏輯 | 無 | set 可驗證 |
| 延遲初始化 | 建構式立即執行 | get 時首次初始化 |
| 副作用/通知 | 無 | get/set 可執行邏輯 |
| 介面統一性 | 直接存取 | get/set 統一名稱 |
IL 層面屬性編譯為 get_Prop/set_Prop 兩方法,語法糖極大提升可讀性。
不佳:public Contacts Contacts = new Contacts(); (公開欄位,無控制)
推薦:
private Contacts _contacts;
public Contacts Contacts
{
get { if (_contacts == null) _contacts = new Contacts(); return _contacts; }
set => _contacts = value;
}
實現 customers[i].Contacts[x].Summary 連續存取,並支援延遲初始化。
get 中判斷 null 建立,是常見平衡策略。var custs = new Customers();
custs.Add(new Customer("Peter"));
custs.Add(new Customer("Linnet"));
Console.WriteLine(custs[0].Name); // 索引器
Console.WriteLine(custs.Item(1).Name); // 方法
Console.WriteLine(custs.Item[1].Name); // Item 屬性+索引器
custs.Remove(0);
Console.WriteLine(custs[0].Name); // Linnet
custs.Clear();
繼承 CollectionBase 適合快速建立強型別唯讀集合;封裝 ArrayList 適合需完整控制增刪改查、型別安全與元素替換語意的場景。善用索引器、屬性、IndexerName 屬性與延遲初始化,能打造既符合 .NET 慣例、又具備領域表達力的集合類別。