類別庫設計中常見需求:內部集合需對外公開供讀取,但禁止外部修改。直接公開 List<T> 會破壞封裝;回傳陣列 T[] 需複製且失去介面豐富度。ReadOnlyCollection<T> 為此提供標準解決方案:包裝任意 IList<T>,暴露唯讀介面,零複製成本。
public class Lottery
{
private List<int> _numbers = null;
public Lottery()
{
_numbers = new List<int>(5);
_numbers.Add(17);
_numbers.Add(21);
_numbers.Add(32);
_numbers.Add(44);
_numbers.Add(58);
}
public ReadOnlyCollection<int> Results
{
get { return new ReadOnlyCollection<int>(_numbers); }
}
}
var lottery = new Lottery();
// 讀取 - 完全正常
for (int i = 0; i < lottery.Results.Count; i++)
Console.WriteLine($"Lottery Number {i} is {lottery.Results[i]}");
// 寫入 - 編譯期錯誤
// lottery.Results[0] = 29; // CS0200: Property or indexer '...' cannot be assigned to -- it is read only
| 特性 | 說明 |
|---|---|
| 包裝目標 | 任意實作 IList<T> 的集合(List<T>、陣列 T[]、自訂集合) |
| 介面實現 | IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable |
| 修改操作 | 所有 Add/Remove/Clear/Insert/this[].set 拋出 NotSupportedException |
| 讀取操作 | Count, IndexOf, Contains, this[].get, GetEnumerator 正常運作 |
| 底層資料 | 共享同一陣列/清單,非複製,修改底層集合會反映到唯讀檢視 |
| 建構成本 | O(1),僅建立包裝物件 |
int[] items = { 0, 1, 2 };
var readOnly = new ReadOnlyCollection<int>(items);
// readOnly[0] = 9; // 編譯錯誤
// items[0] = 9; // 允許,且 readOnly[0] 會變為 9
public class Repository
{
private List<Entity> _entities = new List<Entity>();
// 公開唯讀檢視
public IReadOnlyList<Entity> Entities => new ReadOnlyCollection<Entity>(_entities);
// 或 .NET 4.5+ 直接使用 IReadOnlyList<T> 介面
// public IReadOnlyList<Entity> Entities => _entities.AsReadOnly(); // List 擴充方法
}
優勢:
IReadOnlyList<T> / ReadOnlyCollection<T>,呼叫端無需學習多種 APIList、LinkedList、陣列),不影響公開介面若需「底層資料也不得修改」的深度不可變性,請使用 System.Collections.Immutable (NuGet) 提供的 ImmutableList<T>、ImmutableDictionary<TKey, TValue> 等。修改操作會回傳新實例,結構共享保證效能。
ReadOnlyCollection<T> 是 .NET 框架提供的標準唯讀包裝器,以「零複製、介面完整、編譯期保護」三大優勢,成為類別庫公開集合屬性的最佳實踐。配合 IReadOnlyList<T> 介面(.NET 4.5+),可構建清晰、安全、可演進的 API 介面契約。