如何公开集合属性?
每次我创建一个具有collection属性的对象时,我都会以最佳方式来回移动吗?
- 公共属性,带有一个返回对私有变量的引用的getter
- 显式的get_ObjList和set_ObjList方法,每次返回并创建新对象或者克隆对象
- 显式的get_ObjList返回IEnumerator,而set_ObjList返回IEnumerator
如果集合是数组(即objList.Clone())与列表相比,会有所不同吗?
如果返回实际的集合作为引用非常糟糕,因为它创建了依赖关系,那么为什么要返回任何属性作为参考呢?每当我们将子对象作为参考公开时,就可以更改该子对象的内部,而无需父"知道",除非该子对象具有属性更改事件。有内存泄漏的风险吗?
而且,选项2和3不会中断序列化吗?这是一个陷阱22还是我们必须在拥有collection属性的任何时候都必须实现自定义序列化?
通用的ReadOnlyCollection似乎是通用的不错的折衷方案。它包装一个IList并限制对其的访问。也许这有助于内存泄漏和序列化。但是它仍然有枚举的问题
也许只是视情况而定。如果我们不关心集合是否被修改,则只需按#1将其公开为私有变量上的公共访问器即可。如果我们不希望其他程序修改集合,则#2和/或者#3更好。
问题的隐含含义是,为什么应该在一种方法之上使用另一种方法,以及在安全性,内存,序列化等方面的后果是什么?
解决方案
回答
如果我们只是想在实例上公开一个集合,那么对我的私有成员变量使用getter / setter似乎对我来说是最明智的解决方案(第一个建议选项)。
回答
我通常这样做,它是一个返回System.Collections.ObjectModel.ReadOnlyCollection的公共获取器:
public ReadOnlyCollection<SomeClass> Collection { get { return new ReadOnlyCollection<SomeClass>(myList); } }
并在对象上修改公共方法的集合。
Clear(); Add(SomeClass class);
如果该类应该作为其他人的存储库,那么我只是按照方法1公开私有变量,因为它节省了编写自己的API的时间,但是我倾向于在生产代码中回避它。
回答
我是Java开发人员,但我认为这对于c#是相同的。
我从不公开私有集合属性,因为程序的其他部分可以在没有父级通知的情况下对其进行更改,因此在getter方法中,我返回了一个包含集合对象的数组,而在setter方法中,我调用了clearAll()集合,然后是addAll()
回答
公开集合的方式完全取决于用户与之交互的方式。
1)如果用户要从对象的集合中添加和删除项目,则最好使用简单的仅获取集合属性(原始问题中的选项1):
private readonly Collection<T> myCollection_ = new ...; public Collection<T> MyCollection { get { return this.myCollection_; } }
该策略用于WindowsForms和WPF的ItemsControl控件上的Items集合,用户可以在其中添加和删除希望控件显示的项目。这些控件发布实际的集合,并使用回调或者事件侦听器来跟踪项目。
WPF还公开了一些可设置的集合,以允许用户显示他们控制的项目的集合,例如ItemsControl上的ItemsSource属性(原始问题中的选项3)。但是,这不是常见的用例。
2)如果用户将仅读取对象维护的数据,则可以使用一个只读集合,如Quibblesome建议的那样:
private readonly List<T> myPrivateCollection_ = new ...; private ReadOnlyCollection<T> myPrivateCollectionView_; public ReadOnlyCollection<T> MyCollection { get { if( this.myPrivateCollectionView_ == null ) { /* lazily initialize view */ } return this.myPrivateCollectionView_; } }
请注意,ReadOnlyCollection <T>
提供了底层集合的实时视图,因此我们只需创建一次视图。
如果内部集合未实现IList <T>
,或者我们想限制对更高级用户的访问,则可以通过枚举器包装对集合的访问:
public IEnumerable<T> MyCollection { get { foreach( T item in this.myPrivateCollection_ ) yield return item; } }
这种方法易于实现,并且可以在不暴露内部集合的情况下提供对所有成员的访问。但是,这确实需要使集合保持未修改状态,因为如果在修改集合后尝试枚举集合,则BCL集合类将引发异常。如果基础集合可能会更改,则可以创建一个轻包装器来安全地枚举该集合,也可以返回该集合的副本。
3)最后,如果我们需要公开数组而不是更高级别的集合,则应返回该数组的副本以防止用户对其进行修改(原始问题中的选项#2):
private T[] myArray_; public T[] GetMyArray( ) { T[] copy = new T[this.myArray_.Length]; this.myArray_.CopyTo( copy, 0 ); return copy; // Note: if you are using LINQ, calling the 'ToArray( )' // extension method will create a copy for you. }
我们不应通过属性公开基础数组,因为我们将无法知道用户何时对其进行修改。要允许修改数组,可以添加相应的SetMyArray(T [] array)
方法,或者使用自定义索引器:
public T this[int index] { get { return this.myArray_[index]; } set { // TODO: validate new value; raise change event; etc. this.myArray_[index] = value; } }
(当然,通过实现自定义索引器,我们将复制BCL类的工作:)
回答
我们为什么建议使用ReadOnlyCollection(T)是一种折衷方案?如果仍然需要在原始包装的IList上获得更改通知,则还可以使用ReadOnlyObservableCollection(T)来包装集合。在方案中,这会不会是一个妥协的解决方案?