如何公开集合属性? [关闭]

Posted

技术标签:

【中文标题】如何公开集合属性? [关闭]【英文标题】:How to expose a collection property? [closed] 【发布时间】:2010-09-07 07:02:24 【问题描述】:

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

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

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

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

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

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

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

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

【问题讨论】:

【参考方案1】:

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

1)如果用户将在对象的集合中添加和删除项目,那么最好使用简单的 get-only 集合属性(来自原始问题的选项 #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&gt; 提供了底层集合的实时视图,因此您只需创建一次视图。

如果内部集合没有实现IList&lt;T&gt;,或者如果您想限制对更高级用户的访问,您可以改为通过枚举器包装对集合的访问:

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

【讨论】:

请注意,数组会导致对象的装箱和拆箱,这非常耗费资源 如果数组是强类型的(即int[]、long[]等),访问元素不会导致装箱。仅当您使用 object[] 来存储值类型时才会发生这种情况(即 object[] a = new 1, 2, 3 )。 啊,公平点,你改进了我的建议。好东西! :) 实现您在解决方案 2 中提供的第一个方法将导致“A 字段初始化程序无法引用非静态字段、方法或属性”错误,由 ***.com/questions/923343/… 解释。通过不将 myPrivateCollectionView_ 声明为只读,可以在第一次使用 MyCollection 属性时设置它。这也更有效,因为 myPrivateCollectionView_ 仅在实际需要时创建和维护。 @jphofmann:是的,避免明显的编译错误,即使是在纯说明性代码中,也是一个好主意 :) 我已经更新了 #2 的第一个示例代码,其中包含您关于延迟初始化视图的观点.【参考方案2】:

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

public ReadOnlyCollection<SomeClass> Collection

    get
    
         return new ReadOnlyCollection<SomeClass>(myList);
    

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

Clear();
Add(SomeClass class);

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

【讨论】:

这会在每次读取 Collection 属性时创建新的 ReadOnlycollection,这可能会占用大量资源 是的,皇帝四十二改进了他发布的上述示例中的前提。 @Ivan 实际上不,它根本不占用资源,因为调用此构造函数是 O(1) 操作,这只是一个包装器。 msdn.microsoft.com/en-us/library/ms132476(v=vs.110).aspx 我应该更具体。我在考虑包装器本身。每次调用此属性都会创建一个新包装器。虽然这是 O(1),但这会消耗内存并增加垃圾收集器的压力。在循环调用此属性的某些情况下,您可能会以将大量数据推送到不需要存在的第 2 代来结束。这是一个小问题,而且这种情况很少见。回想起来,我认为我根本不应该发布原始评论:)。【参考方案3】:

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

【讨论】:

【参考方案4】:

我建议使用新的 IReadOnlyList&lt;T&gt;IReadOnlyCollection&lt;T&gt; 接口来公开集合(需要 .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&lt;T&gt; 或新的不可变集合。

避免使用接口IEnumerable&lt;T&gt; 公开集合。 此接口不定义任何保证多个枚举执行良好。如果 IEnumerable 表示一个查询,那么每个枚举都会再次执行该查询。获得 IEnumerable 实例的开发人员不知道它是代表集合还是查询。

可以在Wiki page上阅读有关此主题的更多信息。

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

以上是关于如何公开集合属性? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

缺少公开可见类型或成员的 XML 注释

如何公开公开控件的属性?

如何在普罗米修斯中公开 nginx 指标? [关闭]

如何使用 jQuery 插件公开公共属性?

如何在azure VM上公开HTTP端口[关闭]

WCF 服务:可用于测试的公开可用服务 [关闭]