字典方法 Remove 和 Clear (.NET Core) 在枚举期间修改集合。没有抛出异常

Posted

技术标签:

【中文标题】字典方法 Remove 和 Clear (.NET Core) 在枚举期间修改集合。没有抛出异常【英文标题】:Dictionary methods Remove and Clear (.NET Core) modify the collection during enumeration. No exception thrown 【发布时间】:2020-02-25 06:35:00 【问题描述】:

我正在尝试为enumerating collections safely 实现缓存机制,并且我正在检查内置集合的所有修改是否触发了InvalidOperationException 以由它们各自的枚举器抛出。我注意到在 .NET Core 平台中,Dictionary.RemoveDictionary.Clear 方法不会触发此异常。这是错误还是功能?

Remove 为例:

var dictionary = new Dictionary<int, string>();
dictionary.Add(1, "Hello");
dictionary.Add(2, "World");
foreach (var entry in dictionary)

    var removed = dictionary.Remove(entry.Key);
    Console.WriteLine($"entry removed: removed");

Console.WriteLine($"Count: dictionary.Count");

输出:

[1, Hello] 已移除:True [2, World] 移除:True 计数:0

Clear 为例:

var dictionary = new Dictionary<int, string>();
dictionary.Add(1, "Hello");
dictionary.Add(2, "World");
foreach (var entry in dictionary)

    Console.WriteLine(entry);
    dictionary.Clear();

Console.WriteLine($"Count: dictionary.Count");

输出:

[1,你好] 计数:0

预期的异常是:

InvalidOperationException:集合已修改;枚举操作可能无法执行。

...由方法 Add 和 .NET Framework 中的相同方法抛出。

.NET Core 3.0.0、C# 8、VS 2019 16.3.1、Windows 10

【问题讨论】:

确认:.Net 完整框架演示在这里:dotnetfiddle.net/8vONOw; .Net 核心在这里摆弄:dotnetfiddle.net/es6STm。更奇怪的是,.Net 核心抛出 SortedDictionary&lt;int, string&gt;() 如下所示:dotnetfiddle.net/bs-s-rG7。您可能会报告问题。 【参考方案1】:

对于Dictionary&lt;TKey, TValue&gt;,这似乎是 .Net 完整框架和 .Net 核心之间的故意区别。

分歧发生在Pull #18854: Remove version increment from Dictionary.Remove overloads:

从移除操作中移除版本增量

这解决了 api 更改 Add Dictionary.Remove(predicate) 的 coreclr 方面,目的是允许从字典中删除项目,同时从 @jkotas 枚举每个方向。所有集合测试以及相关 corefx PR 中添加的修改和新测试。

似乎存在未解决的文档问题:

Issue #42123: Clarify Dictionary behavior/guarantees around mutation during enumeration:

说字典的当前实现支持迭代期间的非并发变异是否正确?

仅删除。这是 dotnet/coreclr#18854 中的一项功能。

这是可以依靠的东西吗

是的。

我们应该确保更新文档以反映这一点。

您可能希望对开放文档问题进行投票以要求澄清,因为.Net core 3.0 documentation for Dictionary&lt;TKey,TValue&gt;.GetEnumerator() 现在已过时

如果对集合进行了更改,例如添加、修改或删除元素,枚举器将不可恢复地失效,并且下一次调用MoveNextIEnumerator.Reset 将引发InvalidOperationException .

奇怪的是,SortedDictionary&lt;TKey, TValue&gt; 的枚举器确实在枚举期间修改字典时抛出。

演示:

.Net 框架Remove():https://dotnetfiddle.net/8vONOw(抛出)。 .Net coreRemove():https://dotnetfiddle.net/es6STm(不抛出)。 .Net core Add(): https://dotnetfiddle.net/6q7Lvx(抛出)。 .Net core Remove() from SortedDictionary&lt;int, string&gt;: https://dotnetfiddle.net/bs-s-rG7(抛出)。

【讨论】:

这里的错误报告:github.com/dotnet/corefx/issues/42212 @dev-masih Checking _version 似乎已被有意删除。见Allow Dictionary<K,V>.Remove during enumeration。 @BACON - 是的,我刚刚找到了拉取请求并在您发表评论时更新了我的答案。 另一个奇怪的地方:Dictionary.TrimExcess 方法,仅在 .NET Core 上可用,确实 导致“集合已修改”异常,而没有实际修改 Dictionary @TheodorZoulias - 可能是因为在实践中Dictionary&lt;TKey, TValue&gt; 中删除条目不会中断正在进行的迭代(因为活动条目位于链接列表中) ,但修剪多余的部分是因为它会重新散列所有内容。顺便说一句,我的回答并不是对变化的认可,我只是在报告它。【参考方案2】:

澄清何时提供更改以及通过哪些方法...

Microsoft 在 Dictionary 的 .Remove 和 .Clear 上的文档已更新:

仅限 .NET Core 3.0+:可以安全地调用此变异方法,而不会使 Dictionary 实例上的活动枚举器无效。这并不意味着线程安全。

.NET Core 3.0 附带 C# 8.0。从那时起,我们只能通过.Remove 和.Clear 在枚举(foreach)期间修改 Dictionary

【讨论】:

以上是关于字典方法 Remove 和 Clear (.NET Core) 在枚举期间修改集合。没有抛出异常的主要内容,如果未能解决你的问题,请参考以下文章

Python字典方法总结

JS字典 Dictionary类

Python基础课:列表方法pop(), remove(), clear()

clear(D)方法

测开之数据类型进阶篇・第二篇《字典和集合的原理应用》

字典方法