关于c#:以只读方式返回集合

关于c#:以只读方式返回集合

Return collection as read-only

我在多线程环境中有一个对象,该对象维护着信息的集合,例如:

1
2
3
4
5
6
7
public IList<string> Data
{
    get
    {
        return data;
    }
}

我目前用ReaderWriterLockSlim包裹了return data;,以保护集合免于共享冲突。 但是,可以肯定的是,我想将集合返回为只读状态,以便调用代码无法更改集合,而仅查看已经存在的内容。 这是可能吗?


如果基础数据存储为列表,则可以使用List(T).AsReadOnly方法。
如果您的数据可以枚举,则可以使用Enumerable.ToList方法将您的集合转换为List并对其调用AsReadOnly。


我对您接受的答案投了赞成票,并同意它-但是我可以给您一些考虑的方法吗?

不要直接返回集合。创建一个准确命名的业务逻辑类,以反映集合的目的。

这样做的主要优点在于您无法向集合中添加代码,因此只要您在对象模型中具有本机"集合",您就始终在整个项目中散布非OO支持代码来访问它。

例如,如果您的托收是发票,则您的代码中可能有3或4个地方可以遍历未付款的发票。您可以使用getUnpaidInvoices方法。但是,当您开始考虑诸如" payUnpaidInvoices(payer,account);"之类的方法时,真正的力量就会发挥作用。

当您传递集合而不是编写对象模型时,您将永远不会经历整个重构类。

另请注意,这会使您的问题特别好。如果您不希望人们更改收藏夹,则您的容器不需要包含任何变种器。如果以后再决定仅在一种情况下实际上必须进行修改,则可以创建一种安全的机制来进行修改。

当您传递本机集合时,如何解决该问题?

而且,本机集合无法使用额外的数据进行增强。下次您发现将(Collection,Extra)传递给多个方法时,您会认识到这一点。它表示"额外"属于包含您的集合的对象。


如果您的唯一目的是使调用代码不会出错,并且在仅应读取其内容时修改集合,则只需返回不支持Add,Remove等的接口即可。为什么不返回< x0>?调用代码将不得不进行强制转换,而在不知道所访问属性的内部的情况下,他们不太可能这样做。

但是,如果您的目的是阻止调用代码观察其他线程的更新,则您必须退回到已经提到的解决方案,根据您的需要执行深层或浅层副本。


我认为您在这里混淆了概念。

ReadOnlyCollection为现有集合提供只读包装,允许您(A类)在调用者(B类)无法修改集合(即无法添加或删除)的前提下传递对集合安全的引用。集合中的任何元素。)

绝对没有线程安全保证。

  • 如果您(A类)在将其作为ReadOnlyCollection分发后继续修改基础集合,则B类将看到这些更改,使任何迭代器无效等,并且通常对集合中的任何常见并发问题持开放态度。 。
  • 此外,如果集合中的元素是可变的,您(A类)和调用者(B类)都将能够更改集合中对象的任何可变状态。

您的实现取决于您的需求:
-如果您不关心呼叫者(B类)是否对集合有任何进一步的更改,则可以克隆该集合,将其分发出去并停止维护。
-如果您确实需要调用方(B类)查看对该集合所做的更改,并且希望此操作是线程安全的,那么您手头上还有更多问题。一种可能性是实现您自己的ReadOnlyCollection的线程安全变体以允许锁定访问,但是如果您要支持IEnumerable,这将是不平凡且不可靠的,并且仍然无法保护您免受IEnumerable中的可变元素的影响。采集。


您要使用yield关键字。您遍历IEnumerable列表,并使用yeild返回结果。这样,消费者就可以在不修改集合的情况下使用。

它看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
List<string> _Data;
public IEnumerable<string> Data
{
  get
  {
    foreach(string item in _Data)
    {
      return yield item;
    }
  }
}

应该注意的是,aku的答案只会保护列表为只读。列表中的元素仍然非常可写。我不知道是否有任何方法可以在将非原子元素放入只读列表之前对其进行克隆而无需对其进行克隆。


您可以使用集合的副本。

1
2
3
4
public IList<string> Data {
get {
    return new List< T >(data);
}}

这样一来,是否更新就无关紧要。


推荐阅读