每次我创建一个具有集合属性的对象时,我都会反复思考最好的方法是什么?

  1. 公共财产带有getter返回私人变量的引用
  2. explicit get_objlist和set_objlist方法每次都会返回并创建新的或克隆的对象
  3. explicit get_objlist返回ienumerator和set_objlist,以ienumerator

如果集合是数组(即 objList.Clone())与列表,有什么区别吗?

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

而且,选项 2 和 3 不会破坏序列化吗?这是第 22 条军规,还是只要拥有集合属性就必须实现自定义序列化?

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

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

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

有帮助吗?

解决方案

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

1) 如果用户要从对象的集合中添加和删除项目,那么一个简单的仅获取集合属性是最好的(原始问题中的选项#1):

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

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

WPF 还公开了一些可设置的集合,以允许用户显示他们控制的项目的集合,例如 ItemsSource 属性于 ItemsControl (原始问题中的选项#3)。然而,这不是常见的用例。


2) 如果用户只读取对象维护的数据,那么您可以使用只读集合,如下所示 狡辩的 建议:

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

其他提示

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

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

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

Clear();
Add(SomeClass class);

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

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

为什么您建议使用 ReadOnlyCollection(T) 是一种折衷方案?如果您仍然需要获取对原始包装的 IList 所做的更改通知,您还可以使用 只读可观察集合(T) 包装您的收藏。在您的场景中,这会不会是一种妥协?

我是一名 java 开发人员,但我认为这对于 c# 来说也是一样的。

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

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

我建议使用新的 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 实例的开发人员不知道它代表的是集合还是查询。

有关此主题的更多信息可以阅读此内容 维基页面.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top