【问题标题】:Detecting modifications with an IEnumerable使用 IEnumerable 检测修改
【发布时间】:2011-11-15 19:16:19
【问题描述】:

我很惊讶尚未以这种格式提出一个问题。

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

这是奇怪的部分:我不是多线程的。我认为我的问题在某个地方存在缺陷,因为这应该很简单。 . .我只想知道源什么时候变了,迭代器什么时候过期了。

非常感谢。

【问题讨论】:

    标签: c# ienumerable ienumerator


    【解决方案1】:

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

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

    【讨论】:

    • 两个答案都非常好,你的只是稍微快了一点,所以我给它打上了“正确”的复选标记。谢谢!!我最终只保留了一个版本号。实现起来非常简单。
    【解决方案2】:

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

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

    让集合拥有一个内部集合,其中包含对未完成枚举数的弱引用。并且每次对集合进行修改时,都会使每个还活着的枚举器无效。

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

    【讨论】:

      【解决方案3】:

      Microsoft 建议对 IEnumerable 集合的任何修改都应使任何现有的 IEnumerator 对象无效,但该策略很少特别有用,有时可能会令人讨厌。如果以不会阻止 IEnumerator 返回与未进行此类修改时返回的数据相同的数据的方式修改集合,则 IEnumerable/IEnumerator 的作者没有理由感到需要引发异常。我会更进一步,并建议在可能的情况下,如果枚举器能够遵守以下约束,则它应该被认为是可取的:

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

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

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

      【讨论】:

        【解决方案4】:

        我还没有找到答案,但作为一种解决方法,我刚刚发现了这样的异常(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;
                        }
                    }
        

        【讨论】:

        • 这似乎非常依赖于精确的错误消息字符串匹配。如果错误消息从一种语言的次要版本更新更改为另一种怎么办?
        • 由于应用程序的目标是为其构建的 dot net 版本,我认为这样的错误消息极不可能被更改。对于将应用程序迁移到较新的 dot net 版本,我认为它仍然不太可能改变,因为它们坚持向后兼容。当然,如果它确实发生了变化,那么异常会被重新抛出,所以至少你会知道它。
        • ...重新考虑,我忽略了异常可能不是 English ,所以在我有办法确保它是,然后我正在编辑我的答案以吞下 InvalidOperationException 。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        • 2020-09-12
        • 1970-01-01
        • 2013-09-22
        相关资源
        最近更新 更多