以只读方式返回集合

时间:2008-09-10 23:43:36

标签: c# .net multithreading collections concurrency

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

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

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

7 个答案:

答案 0 :(得分:28)

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

答案 1 :(得分:19)

如果您的唯一目的是让调用代码不会出错,并且修改集合时应该只读取所有必要的内容就是返回一个不支持Add,Remove等的接口。为什么不归IEnumerable<string>?调用代码必须进行转换,如果不知道他们正在访问的属性的内部,他们不太可能这样做。

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

答案 2 :(得分:19)

我投票支持你接受的答案并同意 - 但是我可以给你一些考虑吗?

不要直接退回收藏品。制作一个反映集合目的的准确命名的业务逻辑类。

这样做的主要优点在于您无法将代码添加到集合中,因此每当您在对象模型中拥有本机“集合”时,您始终会在整个项目中传播非OO支持代码以访问它

例如,如果您的收藏是发票,那么您的代码中可能有3或4个位置,您可以在其中迭代未付款的发票。你可以有一个getUnpaidInvoices方法。然而,当你开始考虑像“payUnpaidInvoices(payer,account);”这样的方法时,真正的力量就会出现。

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

另请注意,这会让您的问题变得特别好。如果您不希望人们更改集合,则您的容器不需要包含mutator。如果你稍后决定只在一个案例中你实际上需要修改它,你可以创建一个安全的机制来执行此操作。

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

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

答案 3 :(得分:11)

我认为你在这里混淆概念。

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

绝对没有线程安全保证。

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

您的实施取决于您的需求:   - 如果您不关心调用者(B类)看到对集合的任何进一步更改,那么您可以克隆集合,将其移出并停止关怀。   - 如果您确实需要调用者(B类)来查看对集合所做的更改,并且您希望这是对线程安全的,那么您手上就会遇到更多问题。一种可能性是实现自己的线程安全的ReadOnlyCollection变体以允许锁定访问,但如果你想支持IEnumerable,这将是非平凡和非高效的,并且仍然不会保护您免受集合中的可变元素的影响。

答案 4 :(得分:3)

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

答案 5 :(得分:3)

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

它看起来像这样:

List<string> _Data;
public IEnumerable<string> Data
{
  get
  {
    foreach(string item in _Data)
    {
      return yield item;
    }
  }
}

答案 6 :(得分:2)

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

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

这样,如果它得到更新并不重要。