【问题标题】:Are there any C# collections where modification does not invalidate iterators?是否存在修改不会使迭代器无效的 C# 集合?
【发布时间】:2011-02-14 18:33:20
【问题描述】:

C# Collections 库中是否存在修改结构不会使迭代器失效的数据结构?

考虑以下几点:

List<int> myList = new List<int>();
myList.Add( 1 );
myList.Add( 2 );
List<int>.Enumerator myIter = myList.GetEnumerator();
myIter.MoveNext();  // myIter.Current == 1
myList.Add( 3 );
myIter.MoveNext();  // throws InvalidOperationException

【问题讨论】:

  • 你能解释一下为什么你需要在阅读的同时修改集合吗?可能还有另一种方法可以做你想做的事。

标签: c# .net collections iterator


【解决方案1】:

使用 for 循环代替 foreach,然后您可以对其进行修改。不过我不建议这样做......

【讨论】:

  • 不过,这并不能回答问题。他问的是迭代器,不一定只是索引集合。
  • 通过添加 ElementAt() 扩展方法,现在可以对所有集合进行索引。我认为这很好地回答了问题....
【解决方案2】:

不,它们不存在。当结构发生变化时,所有 C# 标准集合都会使分子失效。

【讨论】:

    【解决方案3】:

    这样做的唯一方法是在迭代之前复制列表:

    var myIter = new List<int>(myList).GetEnumerator();
    

    【讨论】:

      【解决方案4】:

      根据this MSDN article on IEnumerator,您发现的失效行为是 IEnumerable 的所有实现所必需的。

      只要集合保持不变,枚举数就保持有效。如果 对集合进行更改,例如添加、修改或删除 元素,枚举器不可恢复地失效,下一次调用 MoveNext 或 Reset 会引发 InvalidOperationException。如果集合是 在 MoveNext 和 Current 之间修改,Current 返回它所在的元素 设置为,即使枚举器已经失效。

      【讨论】:

      • 那么,他们违反了自己的准则。 System.Collections.Concurrent 中的所有集合都允许在调用 MoveNext 之间修改集合。
      • 那是因为迭代器会遍历集合的一个快照,所以迭代时快照不会被修改。
      • 这就是文档所说的,我理解为什么这是添加和删除元素的好主意,但我不清楚为什么修改元素会使枚举器无效。我可以理解你为什么会这样实现它,但不明白为什么你应该这样做。
      【解决方案5】:

      是的,看看 .NET 4.0 中的 System.Collections.Concurrent 命名空间。

      请注意,对于此命名空间中的某些集合(例如,ConcurrentQueue&lt;T&gt;),这仅通过在相关集合的“快照”上公开枚举数来实现。

      来自the MSDN documentation on ConcurrentQueue&lt;T&gt;

      枚举代表一个 即时快照 队列的内容。它不是 反映对集合的任何更新 在调用 GetEnumerator 之后。这 枚举器可以安全地同时使用 读取和写入 排队。

      不过,并非所有系列都如此。例如,ConcurrentDictionary&lt;TKey, TValue&gt; 为您提供了一个枚举器,该枚举器在对 MoveNext 的调用之间维护对基础集合的更新。

      来自the MSDN documentation on ConcurrentDictionary&lt;TKey, TValue&gt;

      从返回的枚举器 字典可以安全地同时使用 与读取和写入 字典,但它没有 代表一个瞬间的快照 词典。暴露的内容 通过枚举器可能包含 对字典的修改 在调用 GetEnumerator 之后。

      如果您没有 4.0,那么我认为其他人是对的,并且.NET 没有提供这样的集合。但是,您始终可以通过执行 ConcurrentQueue&lt;T&gt; 所做的相同操作(迭代快照)来构建自己的。

      【讨论】:

      • 完整解答,谢谢!或许你知道 ConcurrentQueue 和 ConcurrentStack 是如何拍摄快照的?他们是否将整个集合复制到一个新对象并枚举一个副本?或者他们有其他聪明的方式来拍摄快照?
      【解决方案6】:

      支持这种行为需要一些相当复杂的内部处理,所以大多数集合不支持这种行为(我不确定Concurrent 命名空间)。

      但是,您可以使用不可变集合很好地模拟这种行为。它们不允许您按设计修改集合,但您可以以稍微不同的方式使用它们,这种处理允许您同时使用枚举器而无需复杂处理(在 @987654323 中实现@集合)。

      您可以轻松实现这样的集合,也可以使用 FSharp.Core.dll 中的 FSharpList&lt;T&gt;(虽然不是 .NET 4.0 的标准部分):

      open Microsoft.FSharp.Collections;
      
      // Create immutable list from other collection
      var list = ListModule.OfSeq(anyCollection);
      // now we can use `GetEnumerable`
      var en = list.GetEnumerable();
      
      // To modify the collection, you create a new collection that adds 
      // element to the front (without actually copying everything)
      var added = new FSharpList<int>(42, list);
      

      不可变集合的好处是您可以使用它们(通过创建副本)而不影响原始集合,因此您想要的行为是“免费的”。如需更多信息,请联系great series by Eric Lippert

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-13
        • 2011-03-20
        • 2011-04-22
        • 2012-06-16
        • 2012-08-09
        • 1970-01-01
        • 2015-09-07
        相关资源
        最近更新 更多