使用IEnumerable检测修改

时间:2011-09-13 02:15:04

标签: c# ienumerable ienumerator

我有一个问题,我很惊讶,还没有被问到这种格式。

如果我有一个基于迭代数据源生成的IEnumerable(并使用yield return语句),如何在通过枚举器访问后检测源何时进行修改?通过GetEnumerator调用生成?

这是一个奇怪的部分:我不是多线程的。我认为我的问题在某处存在缺陷,因为这应该很简单。 。 。我只是想知道源何时发生了变化并且迭代器已经过时了。

非常感谢你。

4 个答案:

答案 0 :(得分:5)

您需要自行处理创建枚举数才能跟踪此信息,或者至少使用yield return;并使用您自己的修改跟踪类型。

例如,大多数框架集合类都保留了“版本”编号。当他们创建一个枚举器时,他们会保留该版本号的快照,并在MoveNext()期间进行检查。您可以在致电yield return XXX;

之前进行相同的检查

答案 1 :(得分:3)

.NET BCL中的大多数集合类都使用版本属性进行更改跟踪。也就是说:枚举器是用版本号(整数)构造的,并且检查版本号的原始源在每次迭代时仍然是相同的(当调用movenext时)。每次进行修改时,集合依次递增版本属性。这种跟踪机制简单而有效。

我见过的其他两种方式是:

让集合持有一个内部集合,其中包含对未完成枚举器的弱引用。并且每次对集合进行修改时,它使每个仍然存活的枚举器无效。

或者在集合中实现事件(INotifyCollectionChanged)并简单地在枚举器中注册该事件。如果引发,则将枚举器标记为无效。这种方法相对容易实现,通用,并且没有太多开销,但需要您的集合支持事件

答案 2 :(得分:1)

Microsoft建议对IEnumerable集合进行任何修改都应该使任何现有的IEnumerator对象无效,但该策略很少特别有用,有时可能会造成麻烦。如果修改集合的方式不会阻止IEnumerator返回与没有这样修改时返回的相同数据,那么IEnumerable / IEnumerator的作者就没有理由感到需要抛出异常。我会更进一步,并建议在可能的情况下,如果一个枚举器能够遵守以下限制条件,那么它应该被认为是可取的:

  1. 在整个枚举期间收集的项目必须只返回一次。
  2. 枚举期间添加或删除的每个项目可能会返回零次或一次,但不会超过一次。如果从集合中移除对象并重新添加,则可以将其视为最初被放置在一个项目中但放入新项目中,因此枚举可以合法地返回旧的,新的,两者或两者。

VisualBasic.Collection类的行为符合上述约束条件;这种行为非常有用,可以通过类枚举并删除符合特定标准的项目。

当然,如果在枚举期间修改了集合,那么设计一个合理的行为可能不一定比抛出异常容易,但对于合理大小的集合,可以通过让枚举器将集合转换为列表并枚举来获得这样的语义。列表的内容。如果需要,特别是在不需要线程安全的情况下,使集合对其枚举器返回的列表保持强或弱引用可能会有所帮助,并且在修改它时无效。另一种选择是在包装类中保存对集合的“真实”引用,并让内部类保持存在多少枚举器的计数(枚举器将获得对真实集合的引用)。如果在枚举器存在的情况下尝试修改集合,请使用副本替换集合实例,然后对其进行修改(副本将以引用计数为零开始)。这样的设计将避免制作列表的冗余副本,除非在没有Dispose的情况下放弃IEnumerator的情况;即使在这种情况下,与涉及WeakReferences或事件的场景不同,任何对象都不会在必要时保持活动状态。

答案 3 :(得分:1)

我还没有找到答案,但作为解决方法,我刚刚捕获了这样的异常(WPF示例):

            while (slideShowOn)
            {

                if (this.Model.Images.Count < 1)
                {
                    break;
                }

                var _bitmapsEnumerator = this.Model.Images.GetEnumerator();

                try
                {
                    while (_bitmapsEnumerator.MoveNext())
                    {
                        this.Model.SelectedImage = _bitmapsEnumerator.Current;


                        Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
                        Thread.Sleep(41);
                    }
                }
                catch (System.InvalidOperationException ex)
                {
// Scratch this bit: the error message isn't restricted to English
//                     if (ex.Message == "Collection was modified; enumeration operation may not execute.")
//                        {
//
//                        }
//                        else throw ex;
                }
            }
相关问题