如何公开一个集合属性?
每次我创build一个具有集合属性的对象时,我都会回到最好的方式来做到这一点?
- 公共属性与getter返回私有variables的引用
- 显式get_ObjList和set_ObjList方法每次返回并创build新的或克隆的对象
- 返回IEnumerator的显式get_ObjList和获取IEnumerator的set_ObjList
如果集合是一个数组(即objList.Clone())与List是否有区别?
如果返回实际的集合作为参考是非常糟糕的,因为它创build了依赖关系,那么为什么要返回任何属性作为参考? 任何时候,如果你暴露一个子对象作为参考,那么这个孩子的内部可以在没有父母“知道”的情况下被改变,除非孩子有一个属性改变的事件。 是否有内存泄漏的风险?
而且,不要select2和3打破序列化? 这是一个捕获22或者你有什么时候你有一个集合属性实现自定义序列化?
通用的ReadOnlyCollection对于一般用途来说似乎是一个很好的妥协。 它包装了一个IList并限制对它的访问。 也许这有助于内存泄漏和序列化。 但是,它仍然有枚举问题
也许只是依靠。 如果你不关心集合是否被修改,那么就把它公开为一个公共的访问者,通过#1的私有variables来公开。 如果你不希望其他程序修改集合,那么#2和/或#3更好。
隐含的问题是为什么一种方法应该被用于另一种方法,以及安全性,内存,序列化等方面的影响是什么?
你如何公开一个集合完全取决于用户如何打算与之交互。
1)如果用户将添加和删除对象的集合中的项目,那么一个简单的只能收集属性是最好的(从原来的问题选项#1):
private readonly Collection<T> myCollection_ = new ...; public Collection<T> MyCollection { get { return this.myCollection_; } }
此策略用于WindowsForms和WPF ItemsControl
控件上的Items
集合,用户添加和删除他们希望控件显示的项目。 这些控件发布实际的集合并使用callback或事件侦听器来跟踪项目。
WPF还公开了一些可设置的集合,以允许用户显示他们控制的项目的集合,例如ItemsControl
上的ItemsSource
属性(来自原始问题的选项#3)。 但是,这不是一个常见的用例。
2)如果用户只能读取由对象维护的数据,那么您可以使用只读集合,因为Quibblesomebuild议:
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>
提供了底层集合的实时视图,因此您只需要创build一次视图。
如果内部集合没有实现IList<T>
,或者要限制对更高级用户的访问,则可以通过枚举器将访问权限包装到集合中:
public IEnumerable<T> MyCollection { get { foreach( T item in this.myPrivateCollection_ ) yield return item; } }
这种方法实施起来很简单,也提供了所有成员的访问权限,而不用公开内部的集合。 但是,它确实要求集合保持不变,因为BCL集合类将在修改后枚举集合时抛出exception。 如果底层的集合可能发生变化,您可以创build一个轻量级的包装来安全地枚举集合,或返回集合的副本。
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类的工作:)
我通常去这个,一个公共getter返回System.Collections.ObjectModel.ReadOnlyCollection:
public ReadOnlyCollection<SomeClass> Collection { get { return new ReadOnlyCollection<SomeClass>(myList); } }
和公共方法上的对象来修改集合。
Clear(); Add(SomeClass class);
如果这个类被认为是其他人混淆的存储库,那么我只是按照方法#1公开私有variables,因为它节省了编写自己的API,但是我倾向于回避生产代码。
如果你只是想在你的实例上公开一个集合,那么使用私有成员variables的getter / setter似乎是对我来说最明智的解决scheme(你的第一个build议选项)。
为什么你build议使用ReadOnlyCollection(T)是一个妥协? 如果您仍然需要获取原始包装的IList上的更改通知,则还可以使用ReadOnlyObservableCollection(T)来包装您的集合。 这会不会在你的情况下妥协?
我是一个Java开发人员,但我认为这是相同的C#。
我永远不会公开一个私有集合属性,因为程序的其他部分可以在没有父注意的情况下更改它,所以在getter方法中,我返回一个包含集合对象的数组,并在setter方法中调用clearAll()
然后一个addAll()
ReadOnlyCollection仍然有一个缺点,即消费者不能确定原来的集合不会在不合时宜的情况下被改变。 相反,你可以使用不可变集合 。 如果你需要做一个改变,而改变原来的你正在给一个修改后的副本。 它被实现的方式与可变集合的性能竞争。 甚至更好,如果你不需要复制原来的几次,然后对每个副本进行一些不同(不兼容)的更改。
我build议使用新的IReadOnlyList<T>
和IReadOnlyCollection<T>
接口来公开一个集合(需要.NET 4.5)。
例:
public class AddressBook { private readonly List<Contact> contacts; public AddressBook() { this.contacts = new List<Contact>(); } public IReadOnlyList<Contact> Contacts { get { return contacts; } } public void AddContact(Contact contact) { contacts.Add(contact); } public void RemoveContact(Contact contact) { contacts.Remove(contact); } }
如果你需要保证集合不能从外部操作,那就考虑ReadOnlyCollection<T>
或者新的Immutable集合。
避免使用接口IEnumerable<T>
来公开一个集合。 这个接口并没有定义任何保证多个枚举的性能。 如果IEnumerable表示查询,则每个枚举都会再次执行查询。 获取IEnumerable实例的开发人员不知道它是代表一个集合还是一个查询。
有关此主题的更多信息可以在此Wiki页面上阅读。