Effective C# Item 23: Avoid Returning References to Internal Class Objects
2007-01-29 09:56
489 查看
Effective C# Item 23: Avoid Returning References to Internal Class Objects
我们知道定义只读属性可以让调用者无法修改该属性的值。但是并不是在所有情况下这种做法都能达到目的。如果我们创建了一个返回引用类型的属性,那么调用者可以对这些对象成员进行任何的操作,包括那些我们不期望的修改操作。考虑下面这段例子:
public class MyBusinessObject
MyBusinessObject bizObj = new MyBusinessObject();
DataSet ds = bizObj.Data;
ds.Tables.Clear(); //删除了所有的表
任何MyBusinessObject的客户程序都可以修改它的内部dataset。我们创建只读属性的目的本来是为了隐藏内部成员,但是客户端却可以对它进行任意操作。这个只读属性在我们的类中打开了一个通道将内部成员的引用暴露给了外部。而我们想要的只读属性,并不是这种可读又可写的属性。
这就是引用类型的神奇。任何引用类型返回的都是对象的引用地址。当我们将对象的引用地址返回给调用者后,调用者就可以完全无视类的只读属性而直接操作对应地址上的引用类型。
很明显,我们应当避免类似的这种情况。例如我们为类创建了一个接口并希望所有的使用者遵循,我们一定不会希望使用者获得或者修改类的内部成员。我们有四种策略可以保护类的内部成员不受到这种的威胁:使用值类型,使用不可变类型,通过接口进行功能限制和使用包装器(wrapper)。
值类型通过接口传递的是本身的拷贝。在客户程序中对拷贝的任何修改都不会反映的类的内部成员上来。客户端可以任意处理值类型的拷贝,这不会带来任何问题。
不可变类型同样是安全的,一个典型的例子就是System.String类。我们可以返回一个string或者任何其它的不可变类型,因为它们是不可被修改的。客户端的任何操作同样不会影响到类的内部成员的安全。
第三种方法是定义接口,通过接口客户程序可以使用内部成员的部分功能。当你创建类的时候,可以定义一组包含类中不同功能性的接口。通过暴露这些功能性接口,可以使内部成员暴露最小化。客户程序只能通过接口来获得我们许可的功能,而不是内部成员的全部功能。对应上例中我们可以通过只暴露DataSet类的IListsource接口的方法来避免对dataset的修改。
对于DataSet来说还有最后一种方法:使用包装器。通过DataViewManager类我们可以获得DataSet中的数据,但可以避免对其进行不必要的操作:
public class MyBusinessObject
public class MyBusinessObject
{
private DataSet _ds;
public IList this[string tableName]
{
get
{
DataView view = _ds.DefaultViewManager.CreateDataView(_ds.Tables[tableName]);
view.AllowNew = false;
view.AllowDelete = false;
view.AllowEdit = false;
return view;
}
}
}
最后我们要将特定数据表的视图通过IList接口返回。我们可以在任何集合上使用IList接口,而不仅限于DataSet。我们不能简单的返回一个DataView,用户可以轻易的改变它的功能属性。我们返回的是一个不可修改的对象列表。通过IList接口,我们可以保证DataView对象不会被外部操作修改。
直接将内部成员的引用暴露给用户并接受用户修改的做法是错误的。我们应当注意在接口中返回的内部成员的引用。用户可能会对这些引用做任意的操作,例如调用这些内部成员的公有方法。我们应当通过接口或者包装的方法来限制这种直接的对内部成员的访问。当我们希望客户端可以修改内部数据时,我们应当使用观察者模式(observer pattern),对用户的操作进行验证和应答。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目录
我们知道定义只读属性可以让调用者无法修改该属性的值。但是并不是在所有情况下这种做法都能达到目的。如果我们创建了一个返回引用类型的属性,那么调用者可以对这些对象成员进行任何的操作,包括那些我们不期望的修改操作。考虑下面这段例子:
public class MyBusinessObject
MyBusinessObject bizObj = new MyBusinessObject();
DataSet ds = bizObj.Data;
ds.Tables.Clear(); //删除了所有的表
任何MyBusinessObject的客户程序都可以修改它的内部dataset。我们创建只读属性的目的本来是为了隐藏内部成员,但是客户端却可以对它进行任意操作。这个只读属性在我们的类中打开了一个通道将内部成员的引用暴露给了外部。而我们想要的只读属性,并不是这种可读又可写的属性。
这就是引用类型的神奇。任何引用类型返回的都是对象的引用地址。当我们将对象的引用地址返回给调用者后,调用者就可以完全无视类的只读属性而直接操作对应地址上的引用类型。
很明显,我们应当避免类似的这种情况。例如我们为类创建了一个接口并希望所有的使用者遵循,我们一定不会希望使用者获得或者修改类的内部成员。我们有四种策略可以保护类的内部成员不受到这种的威胁:使用值类型,使用不可变类型,通过接口进行功能限制和使用包装器(wrapper)。
值类型通过接口传递的是本身的拷贝。在客户程序中对拷贝的任何修改都不会反映的类的内部成员上来。客户端可以任意处理值类型的拷贝,这不会带来任何问题。
不可变类型同样是安全的,一个典型的例子就是System.String类。我们可以返回一个string或者任何其它的不可变类型,因为它们是不可被修改的。客户端的任何操作同样不会影响到类的内部成员的安全。
第三种方法是定义接口,通过接口客户程序可以使用内部成员的部分功能。当你创建类的时候,可以定义一组包含类中不同功能性的接口。通过暴露这些功能性接口,可以使内部成员暴露最小化。客户程序只能通过接口来获得我们许可的功能,而不是内部成员的全部功能。对应上例中我们可以通过只暴露DataSet类的IListsource接口的方法来避免对dataset的修改。
对于DataSet来说还有最后一种方法:使用包装器。通过DataViewManager类我们可以获得DataSet中的数据,但可以避免对其进行不必要的操作:
public class MyBusinessObject
public class MyBusinessObject
{
private DataSet _ds;
public IList this[string tableName]
{
get
{
DataView view = _ds.DefaultViewManager.CreateDataView(_ds.Tables[tableName]);
view.AllowNew = false;
view.AllowDelete = false;
view.AllowEdit = false;
return view;
}
}
}
最后我们要将特定数据表的视图通过IList接口返回。我们可以在任何集合上使用IList接口,而不仅限于DataSet。我们不能简单的返回一个DataView,用户可以轻易的改变它的功能属性。我们返回的是一个不可修改的对象列表。通过IList接口,我们可以保证DataView对象不会被外部操作修改。
直接将内部成员的引用暴露给用户并接受用户修改的做法是错误的。我们应当注意在接口中返回的内部成员的引用。用户可能会对这些引用做任意的操作,例如调用这些内部成员的公有方法。我们应当通过接口或者包装的方法来限制这种直接的对内部成员的访问。当我们希望客户端可以修改内部数据时,我们应当使用观察者模式(observer pattern),对用户的操作进行验证和应答。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目录
相关文章推荐
- Item 26: Avoid Returning References to Internal Class Objects(Effective C#)
- Effective C#之23:Avoid Returning References to Internal Class Objects
- Item 28: Avoid returning "handles" to object internals.(Effective C++)
- FindBugs分析记录May expose internal representation by returning reference to mutable ob
- <Effective C++>:Item 20:以pass-by-reference-to-const替代pass-by-value .
- Effective C# Item 29: Use the new Modifier Only When Base Class Updates Mandate it
- Effective C++ Item 10,11 Have assignment operators return a reference to *this Handle assignment to self in operator =
- Effective C# Item 27: Avoid ICloneable
- [翻译] Effective C++, 3rd Edition, Item 20: 用 pass-by-reference-to-const(传给 const 引用)取代 pass-by-value(传值)(上)
- Effective C# Item 9: Understand the Relationships Among ReferenceEquals(),static Equals(),instance Equals,and operator==
- Effective C++ Item 10 令operator= 返回一个reference to *this
- Effective C++ Item 20 宁以pass-by-reference-to-const替换pass-by-value
- Effective C# Item 24: Prefer Declarative to Imperative Programming
- Effective C# Item 6: Distinguish Between Value Types and Reference Types
- 【转】Effective C# Item13: Initialize Static Class Membe
- Item 23: Prefer non-member non-friend functions to member functions(Effective C++)
- Effective C# Item 2: Prefer readonly to const
- 条款28:避免返回handles指向对象内部的成分(Avoid returning "handles" to objects internals)
- Effective C# Item13: Initialize Static Class Members with Static Constructiors
- Effective C# Item 35: Prefer Overrides to Event Handlers