概述 當方法返回類型或屬性類型為集合時,有些開發者會千篇一律地使用IList集合。然而IList具有集合的所有操作,這意味著調用者不僅可以讀取集合信息,還能夠修改集合。業務需求本來只是為調用者提供一個可讀的集合,例如數據的查詢和展示,但當方法返回IList時,無疑隱式地開放了集合可寫的許可權。此時,我... ...
概述
當方法返回類型或屬性類型為集合時,有些開發者會千篇一律地使用IList<T>集合。然而IList<T>具有集合的所有操作,這意味著調用者不僅可以讀取集合信息,還能夠修改集合。業務需求本來只是為調用者提供一個可讀的集合,例如數據的查詢和展示,但當方法返回IList<T>時,無疑隱式地開放了集合可寫的許可權。此時,我們無法阻止調用者篡改集合元素。
註意:將屬性設定為IList<T>類型時,即使聲明為只讀的,我們仍然無法避免集合元素的篡改。例如
public IList<Person> People{get; private set;}
,People屬性是一個IList集合,它雖然是只讀的,但是People集合裡面的元素可以被修改。
這種情況下,我們應該使用IEnumerable<T>來代替IList<T>。
IList<T>和IEnumerable<T>都可以遍歷集合的元素,IList<T>擁有集合的所有操作方法,包括集合元素的增加、修改和刪除。
而IEnumerable<T>則只有一個GetEnumerator方法(擴展方法除外),它返回一個可用於迴圈訪問集合的IEnumerator<T>對象。
示例
重構前
下麵這段代碼定義了兩個類:Order和OrderLine。
- OrderLines屬性是只讀的
- Order提供了AddOrderLine()和RemoveOrderLine()方法,用於添加和刪除訂單明細
當調用者在取到Order實例時,仍然可以通過IList<T>的Add()或Remove()方法修改OrderLines集合。
假設調用者篡改了OrderLines中的元素,並且通過另外的方法回傳了Order實例,則可能會產生一些bug。
/// <summary> /// 訂單 /// </summary> public class Order { private readonly List<OrderLine> _orderLines = new List<OrderLine>(); private double _orderTotal; public IList<OrderLine> OrderLines { get { return _orderLines; } } public void AddOrderLine(OrderLine orderLine) { _orderTotal += orderLine.Total; _orderLines.Add(orderLine); } public void RemoveOrderLine(OrderLine orderLine) { orderLine = _orderLines.Find(item => item == orderLine); if (orderLine == null) return; _orderTotal -= orderLine.Total; _orderLines.Remove(orderLine); } } /// <summary> /// 訂單明細 /// </summary> public class OrderLine { public double Total { get; set; } }
重構後
重構後,不僅OrderLines集合是只讀的,而且也只能通過AddOrderLine()和RemoveOrderLine()方法來增加或刪除訂單明細。
隱藏代碼/// <summary> /// 訂單 /// </summary> public class Order { private readonly List<OrderLine> _orderLines; private double _orderTotal; public Order(List<OrderLine> orderLines) { _orderLines = orderLines; } /// <summary> /// 訂單明細集合是只讀的,只能通過AddOrderLine()和RemoveOrderLine()來增加或刪除訂單明細 /// </summary> public IEnumerable<OrderLine> OrderLines { get { return _orderLines; } } public double OrderTotal { get { return _orderTotal; } } public void AddOrderLine(OrderLine orderLine) { _orderTotal += orderLine.Total; _orderLines.Add(orderLine); } public void RemoveOrderLine(OrderLine orderLine) { orderLine = _orderLines.Find(item => item == orderLine); if (orderLine == null) return; _orderTotal -= orderLine.Total; _orderLines.Remove(orderLine); } } /// <summary> /// 訂單明細 /// </summary> public class OrderLine { public double Total { get; set; } }
註意:上述代碼有一些瑕疵,OrderLine類沒有重寫Object類的Equals()、GetHashCode()方法。
這行代碼orderLine = _orderLines.Find(item => item == orderLine)會使得orderLine每次都是null。
較為完整的OrderLine如下:
public class OrderLine { public int Id { get; set; } public int OrderId { get; set; } public double Total { get; set; } /// <summary> /// 重寫Equals方法 /// </summary> public override bool Equals(object obj) { if (obj == null || !(obj is OrderLine)) return false; if (ReferenceEquals(this, obj)) return true; var other = (OrderLine) obj; if (IsTransient() && other.IsTransient()) return false; var typeOfThis = GetType(); var typeOfOther = other.GetType(); if (!typeOfThis.IsAssignableFrom(typeOfOther) && !typeOfOther.IsAssignableFrom(typeOfThis)) { return false; } return Id.Equals(other.Id); } /// <summary> /// 重寫GetHashCode方法 /// </summary> /// <returns></returns> public override int GetHashCode() { return Id.GetHashCode(); } /// <summary> /// 是否為瞬時對象 /// </summary> /// <returns></returns> public virtual bool IsTransient() { return EqualityComparer<int>.Default.Equals(Id, default(int)); } /// <summary> /// 提供==操作符,可用於對象比較 /// </summary> public static bool operator ==(OrderLine left, OrderLine right) { if (Equals(left, null)) { return Equals(right, null); } return left.Equals(right); } /// <summary> /// 提供!=操作用,可用於對象比較 /// </summary> public static bool operator !=(OrderLine left, OrderLine right) { return !(left == right); } }
小結
當集合作為返回參數時,應使用適合業務需求的集合類型,不宜提供過多的集合操作給調用者。
【關註】keepfool