Effective C#之23:Avoid Returning References to Internal Class Objects

rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 23: Avoid Returning References to Internal Class Objects

避免返回内部类对象的引用

You'd like to think that a read-only property is read-only and that callers can't modify it. Unfortunately, that's not always the way it works. If you create a property that returns a reference type, the caller can access any public member of that object, including those that modify the state of the property. For example:

你可能认为一个只读属性就是只读的,调用者不能修改它。不幸的是,并不总是这么回事。如果你创建了一个返回引用类型的属性,调用者就可以访问那个对象的任何公共成员,包括可以修改属性状态的那些。例如:

 

  1.    public class MyBusinessObject
  2.     {
  3.       // Read Only property providing access to a private data member:
  4.       private DataSet ds;
  5.       public DataSet Data
  6.       {
  7.         get  { return ds;}
  8.        }
  9. }
  10.  
  11.     // Access the dataset:
  12.     DataSet ds = bizObj.Data;
  13.     // Not intended, but allowed:
  14. ds.Tables.Clear( ); // Deletes all data tables.

Any public client of MyBusinessObject can modify your internal dataset. You created properties to hide your internal data structures. You provided methods to let clients manipulate the data only through known methods, so your class can manage any changes to internal state. And then a read-only property opens a gaping hole in your class encapsulation. It's not a read-write property, where you would consider these issues, but a read-only property.

MyBusinessObject的任何公开客户都可以修改内部的dataset。你创建属性来隐藏内部的数据结构,提供方法来让客户仅仅能通过已知方法来维护数据,那样你的类能管理任何对内部状态的更改。但是,一个只读属性在类的封装性上打开了一个洞口。它只是一个只读属性,不是读-写的属性,因此你需要考虑这些问题。

Welcome to the wonderful world of reference-based systems. Any member that returns a reference type returns a handle to that object. You gave the caller a handle to your internal structures, so the caller no longer needs to go through your object to modify that contained reference.

欢迎来到基于引用的系统的精彩世界。任何返回引用类型的成员都会返回一个那个对象的句柄。你给了调用者一个内部结构的句柄,因此调用者不再需要通过对象就能修改包含的引用了。

Clearly, you want to prevent this kind of behavior. You built the interface to your class, and you want users to follow it. You don't want users to access or modify the internal state of your objects without your knowledge. You've got four different strategies for protecting your internal data structures from unintended modifications: value types, immutable types, interfaces, and wrappers.

显然,你希望阻止这种行为。你为类创建接口,希望用户能遵守。你不希望用户在你不知道的情况下,访问或者修改你的对象的内部状态。为了防止你的内部数据结构被不经意的修改,有4种不同的策略:值类型、不可变性类型、接口、包装器。

Value types are copied when clients access them through a property. Any changes to the copy retrieved by the clients of your class do not affect your object's internal state. Clients can change the copy as much as necessary to achieve their purpose. This does not affect your internal state.

客户通过属性访问值类型时,值是被复制的。客户获得副本上的任何修改不会影响对象的内部状态。客户可以按需尽可能的修改副本来达到他们的目的。这不会影响你的内部状态。

Immutable types, such as System.String, are also safe. You can return strings, or any immutable type, safely knowing that no client of your class can modify the string. Your internal state is safe.

System.String一样的具有不可变性的类型也是安全的。你可以安全的返回string,或者不可变类型,因为没有客户可以修改string。你的内部状态是安全的。

The third option is to define interfaces that allow clients to access a subset of your internal member's functionality (see Item 19). When you create your own classes, you can create sets of interfaces that support subsets of the functionality of your class. By exposing the functionality through those interfaces, you minimize the possibility that your internal data changes in ways you did not intend. Clients can access the internal object through the interface you supplied, which will not include the full functionality of the class. Exposing the IListsource interface pointer in the DataSet is one example of this strategy. The Machiavellian programmers out there can defeat that by guessing the type of the object that implements the interface and using a cast. But programmers who go to that much work to create bugs get what they deserve.

第三个选择是定义接口,允许客户访问你的内部成员功能的一个子集(Item19)。当你创建自己的类型时,可以创建支持类部分功能的接口。通过这些接口暴露功能,使得内部数据在意想不到的情况下被修改的可能性最小。客户可以通过你支持的接口来访问内部对象,这些接口不包含类的所有功能。在DataSet里面暴露IListsource接口指针就是这种策略的一个例子。不择手段的程序员可以通过猜测实现接口的对象的类型,使用强制装换来击破这种策略。但是这样做肯定会造成一些bug

The System.Dataset class also uses the last strategy: wrapper objects. The DataViewManager class provides a way to access the DataSet but prevents the mutator methods available through the DataSet class:

System.DataSet类也使用了最后一条策略:包装对象。DataViewManager类提供了访问DataSet的一种方式,但是阻止通过DataSet类调用易变的方法。

  1. public class MyBusinessObject
  2. {
  3.   // Read Only property providing access to a private data member:
  4.   private DataSet da;
  5.   public DataView thisstring tableName ]
  6.   {
  7.     get
  8.     {
  9.       return da.DefaultViewManager.CreateDataView( da.Tables[ tableName ] );
  10.     }
  11.   }
  12. }
  13.  
  14. // Access the dataset:
  15. DataView list = bizObj[ "customers" ];
  16. foreach ( DataRowView r in list )
  17.   Console.WriteLine( r[ "name" ] );

The DataViewManager creates DataViews to access individual data tables in the DataSet. There is no way for the user of your class to modify the tables in your DataSet through the DataViewManager. Each DataView can be configured to allow the modification of individual data elements. But the client cannot change the tables or columns of data. Read/write is the default, so clients can still add, modify, or delete individual items.

DataViewManager创建了DataView来访问DataSet里面的单独数据表,类的用户没有办法通过DataViewManager来修改DataSet里面的表格。每个DataView可以被配置为允许修改单独的数据元素。但是客户不能修改数据的表或者列。读/写是默认的,因此客户依然能够添加、修改或者删除单独的条目。

Before we talk about how to create a completely read-only view of the data, let's take a brief look at how you can respond to changes in your data when you allow public clients to modify it. This is important because you'll often want to export a DataView to UI controls so that the user can edit the data (see Item 38). You've undoubtedly already used Windows forms data binding to provide the means for your users to edit private data in your objects. The DataTable class, inside the DataSet, raises events that make it easy to implement the observer pattern: Your classes can respond to any changes that other clients of your class have made. The DataTable objects inside your DataSet will raise events when any column or row changes in that table. The ColumnChanging and RowChanging events are raised before an edit is committed to the DataTable. The ColumnChanged and RowChanged events are raised after the change is committed.

在我们讨论如何创建一个完全只读的数据视图之前,让我们大概的来看一下,在允许公共客户修改数据的时候,你应该做出什么反应。这很重要,因为你经常希望向UI控件暴露DataView,那样的话用户可以编辑数据(Item38)。毫无疑问,你已经使用过Windows窗体数据绑定,做为你的用户编辑你对象私有数据的一种方式。DataSet内部的DataTable类产生事件,使得实现观察者模式很容易:你的类能最对客户做出的任何修改做出响应。DataSet内部的DataTable在表格内的任何行或者列发生变化的时候,会产生事件。ColumnChanging RowChanging事件在有编辑提交给DataTable之前发生,ColumnChanged RowChanged在修改提交之后发生。

You can generalize this technique anytime you want to expose internal data elements for modification by public clients, but you need to validate and respond to those changes. Your class subscribes to events generated by your internal data structure. Event handlers validate changes or respond to those changes by updating other internal state.

当你希望暴露内部数据元素,供给公共客户修改时,可以使用这个技术,但是你需要验证修改并做出响应。你的类订阅内部数据结构产生的事件。每个处理者都要验证所做的修改,或者通过更新其它内部状态来做出响应。

Going back to the original problem, you want to let clients view your data but not make any changes. When your data is stored in a DataSet, you can enforce that by creating a DataView for a table that does not allow any changes. The DataView class contains properties that let you customize support for add, delete, modification, or even sorting of the particular table. You can create an indexer to return a customized DataView on the requested table using an indexer:

回到原来的问题上,你希望客户查看你的数据但是不能做任何修改。当你的数据存储在DataSet里面时,你可以强制做到这样,方法是:为表格创建不允许任何修改的DataViewDataView类包含有属性,可以让你根据需要来支持添加、删除、修改甚至对特定的表格进行排序。通过在要求的表格上使用索引器,你可以创建一个索引器来返回一个符合需要的DataView

 

  1.    public class MyBusinessObject
  2.     {
  3.         // Read Only property providing access to a
  4.         // private data member:
  5.         private DataSet ds;
  6.         public IList this[string tableName]
  7.         {
  8.             get
  9.             {
  10.                 DataView view =ds.DefaultViewManager.CreateDataView(ds.Tables[tableName]);
  11.                 view.AllowNew = false;
  12.                 view.AllowDelete = false;
  13.                 view.AllowEdit = false;
  14.                 return view;
  15.             }
  16.         }
  17. }
  18.     // Access the dataset:
  19.     IList dv = bizOjb["customers"];
  20.     foreach (DataRowView r in dv)
  21.         Console.WriteLine(r["name"]);

This final excerpt of the class returns the view into a particular data table through its IList interface reference. You can use the IList interface with any collection; it's not specific to the DataSet. You should not simply return the DataView object. Users could easily enable the editing and add/delete capability again. The view you are returning has been customized to disallow any modifications to the objects in the list. Returning the IList pointer keeps clients from modifying the rights they have been given to the DataView object.

类的最后一段通过它的IList接口引用返回一个特定数据表格的视图。IList接口并不限定在DataSet上,可以在任何集合上使用。你不应该简单的返回DataView对象,因为用户可以很容易获得编辑、添加、删除的能力。你正返回的视图已经进行了定制以便禁止对列表里面的对象进行任何修改。返回IList指针免得客户修改它们被分配的关于DataView对象的权限。

Exposing reference types through your public interface allows users of your object to modify its internals without going through the methods and properties you've defined. That seems counterintuitive, which makes it a common mistake. You need to modify your class's interfaces to take into account that you are exporting references rather than values. If you simply return internal data, you've given access to those contained members. Your clients can call any method that is available in your members. You limit that access by exposing private internal data using interfaces, or wrapper objects. When you do want your clients to modify your internal data elements, you should implement the Observer pattern so that your objects can validate changes or respond to them.

通过公共接口暴露引用类型,允许你的对象的用户,不用通过你已经定义的方法和属性就能修改它的内部。这是常见的错误,看起来是违反直觉的。如果你暴露的是引用而非值,那就需要改变类型的接口。如果你简单的返回内部数据,你就给了别人对这些内部成员进行访问的权力。你的客户可以调用对你的成员有效的任何方法。通过接口或者包装对象暴露私有内部数据可以限制该种访问。当你希望客户修改内部数据元素时,应该实现观察者模式,那样的话,你的对象能够对修改进行验证或者对它们做出相应。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值