48

每次我创建一个具有集合属性的对象时,我都会来回寻找最好的方法?

  1. 带有返回私有变量引用的 getter 的公共属性
  2. 显式的 get_ObjList 和 set_ObjList 方法,每次都返回并创建新的或克隆的对象
  3. 返回 IEnumerator 的显式 get_ObjList 和采用 IEnumerator 的 set_ObjList

如果集合是一个数组(即 objList.Clone())与一个列表,它会有所不同吗?

如果将实际集合作为引用返回是如此糟糕,因为它会创建依赖项,那么为什么要返回任何属性作为引用呢?每当您将子对象公开为引用时,除非子对象具有属性更改事件,否则可以在父对象“不知道”的情况下更改该子对象的内部结构。有内存泄漏的风险吗?

而且,选项 2 和 3 不会破坏序列化吗?这是一个问题 22,还是您必须在拥有集合属性的任何时候实现自定义序列化?

通用的 ReadOnlyCollection 似乎是一般用途的一个不错的折衷方案。它包装了一个 IList 并限制对它的访问。也许这有助于内存泄漏和序列化。但是它仍然存在枚举问题

也许这取决于。如果您不关心集合是否被修改,那么只需将其公开为每个#1 的私有变量上的公共访问器。如果您不希望其他程序修改集合,那么 #2 和/或 #3 会更好。

问题中隐含的是为什么要使用一种方法而不是另一种方法,以及对安全性、内存、序列化等的影响是什么?

4

7 回答 7

56

如何公开集合完全取决于用户打算如何与之交互。

1)如果用户将在对象的集合中添加和删除项目,那么最好使用简单的 get-only 集合属性(来自原始问题的选项#1):

private readonly Collection<T> myCollection_ = new ...;
public Collection<T> MyCollection {
  get { return this.myCollection_; }
}

此策略用于ItemsWindowsForms 和 WPFItemsControl控件上的集合,用户可以在其中添加和删除他们希望控件显示的项目。这些控件发布实际的集合并使用回调或事件侦听器来跟踪项目。

WPF 还公开了一些可设置的集合,以允许用户显示他们控制的项目的集合,例如ItemsSource属性 on 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):

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 类的工作:)

于 2008-09-01T20:46:35.923 回答
3

我通常会这样做,一个返回 System.Collections.ObjectModel.ReadOnlyCollection 的公共 getter:

public ReadOnlyCollection<SomeClass> Collection
{
    get
    {
         return new ReadOnlyCollection<SomeClass>(myList);
    }
}

以及对象上的公共方法来修改集合。

Clear();
Add(SomeClass class);

如果该类应该是其他人的存储库,那么我只需按照方法#1 公开私有变量,因为它可以节省编写自己的 API,但我倾向于在生产代码中回避它。

于 2008-08-29T19:07:44.893 回答
1

ReadOnlyCollection 仍然有一个缺点,即消费者不能确定原始集合不会在不合时宜的时候被更改。相反,您可以使用不可变集合。如果您需要进行更改,则改为更改原件,您将获得修改后的副本。它的实现方式与可变集合的性能具有竞争力。或者,如果您不必多次复制原件以在之后对每个副本进行许多不同(不兼容)的更改,那就更好了。

于 2015-05-18T21:55:21.717 回答
1

我建议使用 newIReadOnlyList<T>IReadOnlyCollection<T>Interfaces 来公开集合(需要 .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 页面上阅读有关此主题的更多信息。

于 2015-10-02T19:57:45.717 回答
0

如果您只是想在您的实例上公开一个集合,那么对私有成员变量使用 getter/setter 对我来说似乎是最明智的解决方案(您提出的第一个选项)。

于 2008-08-29T18:52:57.410 回答
0

我是一名 java 开发人员,但我认为 c# 也是如此。

我从不公开私有集合属性,因为程序的其他部分可以在没有父注意的情况下更改它,因此在 getter 方法中我返回一个包含集合对象的数组,在 setter 方法中我调用clearAll()集合然后调用addAll()

于 2008-08-29T19:50:03.387 回答
0

为什么你建议使用 ReadOnlyCollection(T) 是一种妥协?如果您仍然需要在原始包装的 IList 上获得更改通知,您还可以使用ReadOnlyObservableCollection(T)来包装您的集合。在您的情况下,这会不会是一种妥协?

于 2009-08-05T20:31:31.887 回答