如何公开集合属性?

时间:2008-08-29 18:48:17

标签: c# .net architecture

每次我创建一个具有集合属性的对象时,我都会以最佳方式来回运行?

  1. 带有吸气剂的公共财产 返回对私有变量的引用
  2. 显式get_ObjList和set_ObjList 返回并创建新的或克隆的方法 每次都有对象
  3. 显式get_ObjList,返回一个 IEnumerator和一个set_ObjList 采用IEnumerator
  4. 如果集合是一个数组(即objList.Clone())而不是List,它会有所不同吗?

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

    并且,选项2和3不会中断序列化吗?这是一个catch 22还是你必须在有集合属性的时候实现自定义序列化?

    通用ReadOnlyCollection似乎是一般用途的一个很好的折衷方案。它包装IList并限制对它的访问。也许这有助于内存泄漏和序列化。但它仍有enumeration concerns

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

    问题隐含的是为什么一种方法应该用于另一种方法,以及对安全性,内存,序列化等的影响是什么?

7 个答案:

答案 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