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