21

我在多线程环境中有一个对象,它维护着一组信息,例如:

public IList<string> Data 
{
    get 
    {
        return data;
    }
}

我目前已return data;用 a 包裹ReaderWriterLockSlim以保护集合免受共享违规。但是,为了更加确定,我想将集合作为只读返回,以便调用代码无法对集合进行更改,只能查看已经存在的内容。这是可能吗?

4

7 回答 7

33

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

于 2008-09-10T23:47:57.783 回答
19

如果您唯一的目的是让调用代码不出错,并在它应该只读取所有必要的时候修改集合是返回一个不支持添加、删除等的接口。为什么不返回IEnumerable<string>?调用代码必须强制转换,如果不知道他们正在访问的属性的内部结构,他们不太可能这样做。

但是,如果您的意图是阻止调用代码观察来自其他线程的更新,您将不得不回退到已经提到的解决方案,根据您的需要执行深层或浅层复制。

于 2008-09-11T01:45:52.977 回答
19

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

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

这样做的主要优势在于您不能将代码添加到集合中,因此,只要您的对象模型中有一个本机“集合”,您就总是在整个项目中分布有非 OO 支持代码来访问它。

例如,如果您的收藏是发票,那么您的代码中可能有 3 或 4 个地方迭代未付发票。你可以有一个 getUnpaidInvoices 方法。但是,当您开始考虑诸如“payUnpaidInvoices(payer, account);”之类的方法时,真正的威力就出现了。

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

另请注意,这使您的问题特别好。如果您不希望人们更改集合,则您的容器不需要包含修改器。如果您稍后决定仅在一种情况下您实际上必须修改它,您可以创建一个安全机制来执行此操作。

当你传递一个原生集合时,你如何解决这个问题?

此外,无法使用额外数据增强本机集合。下次当您发现您将 (Collection, Extra) 传递给不止一两个方法时,您就会认识到这一点。它表示“Extra”属于包含您的收藏的对象。

于 2009-12-18T23:47:34.580 回答
11

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

ReadOnlyCollection为现有集合提供只读包装器,允许您(A 类)在知道调用者(B 类)不能修改集合(即不能添加或删除任何元素)的情况下安全地传递集合引用集合。)

绝对没有线程安全保证。

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

您的实现取决于您的需求: - 如果您不关心调用者(B 类)看到对集合的任何进一步更改,那么您可以克隆集合,分发它,然后停止关心。- 如果您确实需要调用者(B 类)查看对集合所做的更改,并且希望它是线程安全的,那么您手上的问题就更大了。一种可能性是实现您自己的 ReadOnlyCollection 线程安全变体以允许锁定访问,尽管如果您想支持 IEnumerable,这将是不平凡且非执行的,并且它仍然不能保护您免受收藏。

于 2008-09-12T17:16:00.720 回答
3

应该注意aku的答案只会保护列表为只读。列表中的元素仍然非常可写。我不知道是否有任何方法可以保护非原子元素而不克隆它们,然后再将它们放入只读列表。

于 2008-09-11T00:22:24.860 回答
3

您想使用yield关键字。您遍历 IEnumerable 列表并使用 yeild 返回结果。这允许消费者在不修改集合的情况下使用 for each。

它看起来像这样:

List<string> _Data;
public IEnumerable<string> Data
{
  get
  {
    foreach(string item in _Data)
    {
      return yield item;
    }
  }
}
于 2008-09-21T08:08:52.867 回答
2

您可以改用该集合的副本。

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

这样,更新就无所谓了。

于 2008-09-21T07:28:07.507 回答