《Effective C#》读书笔记——条目26:避免返回对内部类对象的引用<使用C#表达设计>
2013-01-19 21:07
621 查看
可能你会认为只读属性就只能读取,调用者不可能改变属性值。并非所有的情况都是如此,我们看下面的示例:
本来我们希望MyBusinessOjbect类的Data属性是只读的,但是现在仍然可以通过其返还的引用来改变其内部状态:
显然,这并不是我们希望的行为,我们为类创建了严格的接口,然后让用户通过接口使用对象。我们不希望用户在我们不知道的情况下,访问或者修改对象的内部状态。我们有四种策略可以防止这种类型的内部数据结构遭受有意或无意的修改:
值类型
常量类型
接口
包装器(Wrapper)
如果将引用类型通过公有借口暴露给外界,那么对象的使用者即可绕过我们定义的方法和属性来更改对象的内部结构。这违反了我们通常的直觉,也会导致常见的错误。考虑到这一点,你应该修改类暴露出的接口。如果只是简单的返回内部数据,那么实际上就给外界赋予了访问内部成员的权限。客户代码可以调用成员中任何可用的方法。通过使用接口、包装器对象或值类型向外部提供私有数据,即可限制外界对这些数据的访问能力。
public class MyBusinessObject { private List<string> listOfData = new List<string> {"COM1","COM2","COM3","COM4","COM5","COM6" }; public List<string> Data { get { return listOfData; } } //其他细节省略 }
本来我们希望MyBusinessOjbect类的Data属性是只读的,但是现在仍然可以通过其返还的引用来改变其内部状态:
MyBusinessObject bo = new MyBusinessObject(); //访问集合 List<string> stuff = bo.Data; //不应该这些,但是这里却允许 stuff.Clear();
显然,这并不是我们希望的行为,我们为类创建了严格的接口,然后让用户通过接口使用对象。我们不希望用户在我们不知道的情况下,访问或者修改对象的内部状态。我们有四种策略可以防止这种类型的内部数据结构遭受有意或无意的修改:
值类型
常量类型
接口
包装器(Wrapper)
1.值类型
当客户代码通过属性来访问值类型的成员时,实际返回的是值类型的副本,而对副本的任何修改都不会影响对象的内部状态。2.常量类型
常量类型也是安全的(参见:Effective C# 笔记条目20)。我们可以在类型中安全地返回字符串或者其他的常量类型,客户代码不可能对它们做出任何更改,因为我们也可以保证类型内部状态的安全。3.定义接口
通过定义接口,将客户对内部数据成员的访问权限限制在一部分功能中(参见:Effective C# 笔记条目22)。当我们创建类时,可以创建一组接口来支持类功能的子集。通过接口向外界暴露类的功能,即可尽量地避免内部数据遭到有意或无意的更改。使用IEnumerable接口向外提供List<T>的功能就是这种策略的一个应用。4.提供包装器对象
提供一个包装器对象,仅暴露该包装器,从而限制对其中对象的访问。System.Collections.ObjectModel.ReadOnlyCollection<T>类型就是对集合的一个标准的只读封装,让其中的数据保持只读:public class MyBusinessObject { private List<string> listOfData = new List<string> {"COM1","COM2","COM3","COM4","COM5","COM6" }; public List<string> Data { get { return listOfData; } } public ReadOnlyCollection<string> CollectionOfData { get { return new ReadOnlyCollection<string>(listOfData); } } //其他细节省略 }
小节
如果将引用类型通过公有借口暴露给外界,那么对象的使用者即可绕过我们定义的方法和属性来更改对象的内部结构。这违反了我们通常的直觉,也会导致常见的错误。考虑到这一点,你应该修改类暴露出的接口。如果只是简单的返回内部数据,那么实际上就给外界赋予了访问内部成员的权限。客户代码可以调用成员中任何可用的方法。通过使用接口、包装器对象或值类型向外部提供私有数据,即可限制外界对这些数据的访问能力。
相关文章推荐
- 《Effective C#》读书笔记——条目25:用事件模式实现通知<使用C#表达设计>
- 《Effective C#》读书笔记——条目27:让类型支持序列化<使用C#表达设计>
- 《Effective C#》读书笔记——条目28:提供粗粒度的互联网API<使用C#表达设计>
- 《Effective C#》读书笔记——条目23:理解接口方法和虚方法的区别<使用C#表达设计>
- 《Effective C#》读书笔记——条目22:通过定义并实现接口替代继承<使用C#表达设计>
- 《Effective C#》读书笔记——条目21:限制类型的可见性<使用C#表达设计>
- 《Effective C#》读书笔记——条目24:用委托实现回调<使用C#表达设计>
- 《Effective C#》读书笔记——条目1:使用属性而不是可访问的数据成员<C#语言习惯>
- 《Effective C#》读书笔记——条目3:推荐使用is或as而不是强制转换类型<C#语言习惯>
- 《Effective C#》读书笔记——条目10:使用可选参数减少方法重载的数量<C#语言习惯>
- 《Effective C#》读书笔记——条目11:理解短小方法的优势<C#语言习惯>
- 《Effective C#》读书笔记——条目8:推荐使用查询语法而不是循环<C#语言习惯>
- 《Effective C#》读书笔记——条目4:使用Conditional特性而不是#if条件编译<C#语言习惯>
- Effective C# 原则23:避免返回内部类对象的引用
- 《Effective C#》读书笔记——条目2:用运行时常量而不是编译期常量<C#语言习惯>
- 《Effective C#》读书笔记——条目16:避免创建非必要的对象<.NET资源管理>
- 《Effective C#》读书笔记——条目5:为类型提供ToString()方法<C#语言习惯>
- Effective C# 原则23:避免返回内部类对象的引用(翻译)
- 《Effective C#》读书笔记——条目6:理解几个等同性判断之间的关系<C#语言习惯>
- Effective C# 避免返回内部类对象的引用