每次我创建一个具有集合属性的对象时,我都会以最佳方式来回运行?
如果集合是一个数组(即objList.Clone())而不是List,它会有所不同吗?
如果返回实际集合作为引用是如此糟糕,因为它创建依赖项,那么为什么要返回任何属性作为引用?每当您将子对象作为引用公开时,除非父级具有属性更改事件,否则可以在父级“知道”的情况下更改该子对象的内部。是否有内存泄漏的风险?
并且,选项2和3不会中断序列化吗?这是一个catch 22还是你必须在有集合属性的时候实现自定义序列化?
通用ReadOnlyCollection似乎是一般用途的一个很好的折衷方案。它包装IList并限制对它的访问。也许这有助于内存泄漏和序列化。但它仍有enumeration concerns
也许只是取决于。如果您不关心集合是否被修改,那么只需将它作为公共访问器公开在每个#1的私有变量上。如果您不希望其他程序修改集合,那么#2和/或#3会更好。
问题隐含的是为什么一种方法应该用于另一种方法,以及对安全性,内存,序列化等的影响是什么?
答案 0 :(得分:54)
如何公开集合完全取决于用户与其进行交互的方式。
1)如果用户要添加和删除对象集合中的项目,那么最简单的只获取集合属性(原始问题中的选项#1):
private readonly Collection<T> myCollection_ = new ...;
public Collection<T> MyCollection {
get { return this.myCollection_; }
}
此策略用于WindowsForms和WPF Items
控件上的ItemsControl
集合,用户可在其中添加和删除希望控件显示的项目。这些控件发布实际的集合并使用回调或事件侦听器来跟踪项目。
WPF还公开了一些可设置的集合,以允许用户显示他们控制的项目集合,例如ItemsSource
上的ItemsControl
属性(原始问题中的选项#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):< / p>
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类的工作:)
答案 1 :(得分:3)
我通常会这样做,一个返回System.Collections.ObjectModel.ReadOnlyCollection的公共getter:
public ReadOnlyCollection<SomeClass> Collection
{
get
{
return new ReadOnlyCollection<SomeClass>(myList);
}
}
对象的公共方法修改集合。
Clear();
Add(SomeClass class);
如果该类应该是其他人的存储库,那么我只是按照方法#1公开私有变量,因为它节省了编写自己的API,但我倾向于回避生产代码中的那个。
答案 2 :(得分:0)
如果您只是想在您的实例上公开一个集合,那么将getter / setter用于私有成员变量对我来说似乎是最明智的解决方案(您的第一个提议选项)。
答案 3 :(得分:0)
我是一名java开发人员,但我认为这与c#相同。
我从不公开私有集合属性,因为程序的其他部分可以在没有父级注意的情况下更改它,因此在getter方法中我返回一个包含集合对象的数组,在setter方法中我调用{{1在集合上,然后是clearAll()
答案 4 :(得分:0)
为什么建议使用ReadOnlyCollection(T)是妥协?如果您仍需要在原始包装的IList上进行更改通知,您还可以使用ReadOnlyObservableCollection(T)来包装您的集合。在您的方案中,这不会是妥协吗?
答案 5 :(得分:0)
ReadOnlyCollection仍然有一个缺点,即消费者不能确定原始集合在不合适的时间不会被更改。相反,您可以使用Immutable Collections。如果您需要进行更改,则更改原件,您将获得修改后的副本。它的实现方式与可变集合的性能相竞争。或者甚至更好,如果您不必复制原件几次,以便在每个副本之后进行许多不同(不兼容)的更改。
答案 6 :(得分:0)
我建议使用新的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>
或新的不可变集合。
避免使用接口IEnumerable<T>
来公开集合。
此接口未定义多个枚举执行良好的任何保证。如果IEnumerable表示查询,则每个枚举再次执行查询。获取IEnumerable实例的开发人员不知道它是代表集合还是查询。
有关此主题的更多信息,请参阅此Wiki page。