字典方法 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.Remove
和 Dictionary.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<int, string>()
如下所示:dotnetfiddle.net/bs-s-rG7。您可能会报告问题。
【参考方案1】:
对于Dictionary<TKey, TValue>
,这似乎是 .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<TKey,TValue>.GetEnumerator()
现在已过时:
如果对集合进行了更改,例如添加、修改或删除元素,枚举器将不可恢复地失效,并且下一次调用
MoveNext
或IEnumerator.Reset
将引发InvalidOperationException
.
奇怪的是,SortedDictionary<TKey, TValue>
的枚举器确实在枚举期间修改字典时抛出。
演示:
.Net 框架Remove()
:https://dotnetfiddle.net/8vONOw(抛出)。
.Net coreRemove()
:https://dotnetfiddle.net/es6STm(不抛出)。
.Net core Add()
: https://dotnetfiddle.net/6q7Lvx(抛出)。
.Net core Remove()
from SortedDictionary<int, string>
: 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<TKey, TValue>
中删除条目不会中断正在进行的迭代(因为活动条目位于链接列表中) ,但修剪多余的部分是因为它会重新散列所有内容。顺便说一句,我的回答并不是对变化的认可,我只是在报告它。【参考方案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) 在枚举期间修改集合。没有抛出异常的主要内容,如果未能解决你的问题,请参考以下文章