在 .NET<5 和 .NET Core 3.1 中,以下代码
var d = new Dictionary<string, int> { { "a", 0 }, { "b", 0 }, { "c", 0 } };
foreach (var k in d.Keys)
{
d[k]+=1;
}
Run Code Online (Sandbox Code Playgroud)
投掷
System.InvalidOperationException:集合已修改;枚举操作可能无法执行。
当面向 .NET 5 时,代码段不再抛出。
发生了什么变化?
我未能在Breaking changes in .NET 5和Performance Improvements in .NET 5 中找到答案。
是不是和什么有关ref readonly T?
pin*_*x33 44
对 的源代码进行了更改,Dictionary<TKey, TValue>以允许在枚举期间更新现有密钥。它由 Stephen Toub 于 2020 年 4 月 9 日提交。可以在此处找到该提交以及相应的PR #34667。
PR 的标题是“在枚举期间允许字典覆盖”,并指出它修复了问题 #34606 “考虑_version++从覆盖中删除Dictionary<TKey, TValue>”。Toub 先生开篇的该问题案文如下:
我们之前
_version++从字典中删除了 when Remove'ing。当只是覆盖字典中现有键的值时,我们也应该考虑这样做。这将启用更新循环来调整字典中的值,而无需求助于复杂且更昂贵的措施。
关于该问题的评论询问:
这样做有什么好处?
Stephen Toub回答说:
正如原帖中所说,今天抛出的精细模式将开始正常工作,例如
foreach (KeyValuePair<string, int> pair in dict)dict[pair.Key] = pair.Value + 1;
如果您查看Dictionary<, > 源代码,您可以看到该_version字段(用于检测修改)现在仅在特定条件下更新,而不是在修改现有键时更新。
特别感兴趣的领域是TryInsert方法(由索引器调用,见下文)及其第三个 type 参数InsertionBehavior。当此值为现有密钥时InsertionBehavior.OverwriteExisting,版本控制字段不会更新。
例如,看到这一段代码从更新 TryInsert:
if (behavior == InsertionBehavior.OverwriteExisting)
{
entries[i].value = value;
return true;
}
Run Code Online (Sandbox Code Playgroud)
在更改之前,该部分看起来像这样(我的代码注释):
if (behavior == InsertionBehavior.OverwriteExisting)
{
entries[i].value = value;
_version++; // <-----
return true;
}
Run Code Online (Sandbox Code Playgroud)
请注意,该_version字段的增量已被删除,因此允许在枚举期间进行修改。
为了完整起见,索引器的设置器看起来像这样。此更改并未对其进行修改,但请注意影响上述行为的第三个参数:
set
{
bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting);
Debug.Assert(modified);
}
Run Code Online (Sandbox Code Playgroud)
Remove'ing from the dictionary 也不再影响枚举。然而,自 netcore 3.0 以来一直存在,并在以下文档中Remove适当地指出:
仅适用于 .NET Core 3.0+:可以安全地调用此变异方法,而不会使
Dictionary<TKey,TValue>实例上的活动枚举器失效 。这并不意味着线程安全。
尽管一个开发者在链接的问题,该文件被更新(和这似乎是一个保证,它坚持将定),为索引的文档还没有(2021年4月4日)进行了更新,以反映当前的行为。