我有一个令我惊讶的问题还没有以这种格式被问到。
如果我有一个基于迭代数据源(并使用 yield return 语句)生成的 IEnumerable,我如何检测通过生成的 Enumerator 访问后源何时发生修改GetEnumerator 调用?
这是奇怪的部分:我不是多线程的。我认为我的问题在某个地方存在缺陷,因为这应该很简单。. . 我只想知道源何时更改并且迭代器已过时。
非常感谢。
我有一个令我惊讶的问题还没有以这种格式被问到。
如果我有一个基于迭代数据源(并使用 yield return 语句)生成的 IEnumerable,我如何检测通过生成的 Enumerator 访问后源何时发生修改GetEnumerator 调用?
这是奇怪的部分:我不是多线程的。我认为我的问题在某个地方存在缺陷,因为这应该很简单。. . 我只想知道源何时更改并且迭代器已过时。
非常感谢。
您需要自己处理创建枚举器以跟踪此信息,或者至少使用yield return;
您自己的修改跟踪类型。
例如,大多数框架集合类都保留一个“版本”号。当他们创建一个枚举器时,他们会保留该版本号的快照,并在MoveNext()
. 你可以在打电话之前做同样的检查yield return XXX;
.NET BCL 中的大多数集合类都使用版本属性进行更改跟踪。即:枚举器是用版本号(整数)构造的,并且每次迭代(调用 movenext 时)检查版本号的原始来源是否仍然相同。每次进行修改时,该集合依次递增版本属性。这种跟踪机制简单有效。
我见过的其他两种方式是:
让集合持有一个内部集合,其中包含对未完成的枚举数的弱引用。并且每次对集合进行修改时,都会使每个仍然活着的枚举数无效。
或者在集合中实现事件( INotifyCollectionChanged )并简单地在枚举器中注册该事件。如果提出,则将枚举数标记为无效。此方法相对容易实现,通用且没有太多开销,但需要您的集合来支持事件
Microsoft 建议对 IEnumerable 集合的任何修改都应使任何现有的 IEnumerator 对象无效,但该策略很少特别有用,有时可能会令人讨厌。如果以不会阻止 IEnumerator 返回与未进行此类修改时返回的数据相同的数据的方式修改集合,则 IEnumerable/IEnumerator 的作者没有理由感到需要引发异常。我会更进一步,并建议在可能的情况下,如果枚举器能够遵守以下约束条件,它应该被认为是可取的:
VisualBasic.Collection 类的行为符合上述约束;这种行为可能非常有用,可以枚举类并删除满足特定标准的项目。
当然,如果在枚举期间对其进行了修改,将集合设计成合理的行为可能并不一定比抛出异常容易,但对于合理大小的集合,可以通过让枚举器将集合转换为列表并枚举内容来获得这样的语义。名单。如果需要,尤其是在不需要线程安全的情况下,让集合保持对其枚举器返回的列表的强引用或弱引用可能会有所帮助,并在任何修改时使此类引用无效。另一种选择是将集合的“真实”引用保存在包装类中,并让内部类计算存在多少枚举器(枚举器将获得对真实集合的引用)。如果在枚举器存在时尝试修改集合,用副本替换集合实例,然后对其进行修改(副本将从引用计数为零开始)。这样的设计将避免制作列表的冗余副本,除非在 IEnumerator 被放弃而不被 Dispose 的情况下;即使在那种情况下,与涉及 WeakReference 或事件的情况不同,任何对象都不会保持活动的时间超过必要的时间。
我还没有找到答案,但作为一种解决方法,我刚刚发现了这样的异常(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;
}
}