可能你會認為唯讀屬性就只能讀取,調用者不可能改變屬性值。並非所有的情況都是如此,我們看下面的樣本:
1 public class MyBusinessObject 2 { 3 private List<string> listOfData = new List<string> {"COM1","COM2","COM3","COM4","COM5","COM6" }; 4 5 public List<string> Data 6 { 7 get { return listOfData; } 8 } 9 //其他細節省略10 }
本來我們希望MyBusinessOjbect類的Data屬性是唯讀,但是現在仍然可以通過其返還的引用來改變其內部狀態:
1 MyBusinessObject bo = new MyBusinessObject();2 //訪問集合3 List<string> stuff = bo.Data;4 //不應該這些,但是這裡卻允許5 stuff.Clear();
顯然,這並不是我們希望的行為,我們為類建立了嚴格的介面,然後讓使用者通過介面使用對象。我們不希望使用者在我們不知道的情況下,訪問或者修改對象的內部狀態。我們有四種策略可以防止這種類型的內部資料結構遭受有意或無意的修改:
- 實值型別
- 常量類型
- 介面
- 封裝器(Wrapper)
1.實值型別
當客戶代碼通過屬性來訪問實值型別的成員時,實際返回的是實值型別的副本,而對副本的任何修改都不會影響對象的內部狀態。
2.常量類型
常量類型也是安全的(參見:Effective C# 筆記條目20)。我們可以在類型中安全地返回字串或者其他的常量類型,客戶代碼不可能對它們做出任何更改,因為我們也可以保證類型內部狀態的安全。
3.定義介面
通過定義介面,將客戶對內部資料成員的存取權限限制在一部分功能中(參見:Effective C# 筆記條目22)。當我們建立類時,可以建立一組介面來支援類功能的子集。通過介面向外界暴露類的功能,即可盡量地避免內部資料遭到有意或無意的更改。使用IEnumerable介面向外提供List<T>的功能就是這種策略的一個應用。
4.提供封裝器對象
提供一個封裝器對象,僅暴露該封裝器,從而限制對其中對象的訪問。System.Collections.ObjectModel.ReadOnlyCollection<T>類型就是對集合的一個標準的唯讀封裝,讓其中的資料保持唯讀:
1 public class MyBusinessObject 2 { 3 private List<string> listOfData = new List<string> {"COM1","COM2","COM3","COM4","COM5","COM6" }; 4 5 public List<string> Data 6 { 7 get { return listOfData; } 8 } 9 10 public ReadOnlyCollection<string> CollectionOfData11 {12 get { return new ReadOnlyCollection<string>(listOfData); }13 }14 //其他細節省略15 }
小節
如果將參考型別通過公有借口暴露給外界,那麼對象的使用者即可繞過我們定義的方法和屬性來更改對象的內部結構。這違反了我們通常的直覺,也會導致常見的錯誤。考慮到這一點,你應該修改類暴露出的介面。如果只是簡單的返回內部資料,那麼實際上就給外界賦予了訪問內部成員的許可權。客戶代碼可以調用成員中任何可用的方法。通過使用介面、封裝器對象或實值型別向外部提供私人資料,即可限制外界對這些資料的訪問能力。