如何公开集合属性?

时间:2020-03-05 18:45:09  来源:igfitidea点击:

每次我创建一个具有collection属性的对象时,我都会以最佳方式来回移动吗?

  • 公共属性,带有一个返回对私有变量的引用的getter
  • 显式的get_ObjList和set_ObjList方法,每次返回并创建新对象或者克隆对象
  • 显式的get_ObjList返回IEnumerator,而set_ObjList返回IEnumerator

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

如果返回实际的集合作为引用非常糟糕,因为它创建了依赖关系,那么为什么要返回任何属性作为参考呢?每当我们将子对象作为参考公开时,就可以更改该子对象的内部,而无需父"知道",除非该子对象具有属性更改事件。有内存泄漏的风险吗?

而且,选项2和3不会中断序列化吗?这是一个陷阱22还是我们必须在拥有collection属性的任何时候都必须实现自定义序列化?

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

也许只是视情况而定。如果我们不关心集合是否被修改,则只需按#1将其公开为私有变量上的公共访问器即可。如果我们不希望其他程序修改集合,则#2和/或者#3更好。

问题的隐含含义是,为什么应该在一种方法之上使用另一种方法,以及在安全性,内存,序列化等方面的后果是什么?

解决方案

回答

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

回答

我通常这样做,它是一个返回System.Collections.ObjectModel.ReadOnlyCollection的公共获取器:

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

并在对象上修改公共方法的集合。

Clear();
Add(SomeClass class);

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

回答

我是Java开发人员,但我认为这对于c#是相同的。

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

回答

公开集合的方式完全取决于用户与之交互的方式。

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

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

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

WPF还公开了一些可设置的集合,以允许用户显示他们控制的项目的集合,例如ItemsControl上的ItemsSource属性(原始问题中的选项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 &lt;T>提供了底层集合的实时视图,因此我们只需创建一次视图。

如果内部集合未实现IList &lt;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类的工作:)

回答

我们为什么建议使用ReadOnlyCollection(T)是一种折衷方案?如果仍然需要在原始包装的IList上获得更改通知,则还可以使用ReadOnlyObservableCollection(T)来包装集合。在方案中,这会不会是一个妥协的解决方案?