使用IEnumerable检测修改

本文关键字:修改 检测 IEnumerable 使用 | 更新日期: 2023-09-27 18:08:29

我有一个问题,我很惊讶还没有人以这种格式问过我。

如果我有一个基于迭代数据源生成的IEnumerable(并使用yield返回语句),我如何检测通过通过GetEnumerator调用生成的Enumerator访问后对源进行了修改?

这里是奇怪的部分:我不是多线程。我想我的问题在某个地方有瑕疵,因为这应该很简单…我只是想知道什么时候源代码改变了,迭代器过期了。

使用IEnumerable检测修改

为了跟踪这些信息,您需要自己处理创建枚举器的问题,或者,至少在适当的位置使用带有您自己类型的修改跟踪的yield return;

例如,大多数框架集合类都会保留一个"版本"号。当他们创建枚举器时,他们保留该版本号的快照,并在MoveNext()期间检查它。您可以在调用yield return XXX;
之前进行相同的检查。

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

我见过的另外两种方式是:

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

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

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

  1. 在枚举持续时间内处于集合中的项必须只返回一次。
  2. 枚举过程中添加或删除的每个项可以返回0次或1次,但不超过1次。如果从集合中删除对象并重新添加对象,则可以将其视为最初包含在一个项目中,但放入新项目中,因此枚举可以合法地返回旧项目,新项目,两者都返回,或者两者都不返回。

VisualBasic。集合类根据上述约束行为;这样的行为非常有用,可以枚举整个类并删除符合特定条件的项。

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

我还没有找到答案,但作为一种工作,我刚刚捕捉到这样的异常(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;
                }
            }