当前位置:首页 > 科技  > 软件

Foreach 集合又抛经典异常了,这次一定要刨根问底

来源: 责编: 时间:2024-07-09 07:04:57 93观看
导读一、背景1. 讲故事最近同事在写一段业务逻辑的时候,程序跑起来总是报:集合已修改;可能无法执行枚举操作,硬是没有找到什么情况下会导致这个异常产生,就让我来找一下bug,其实这个异常在座的每个程序员几乎都遇到过,谁也不是一

一、背景

1. 讲故事

最近同事在写一段业务逻辑的时候,程序跑起来总是报:集合已修改;可能无法执行枚举操作,硬是没有找到什么情况下会导致这个异常产生,就让我来找一下bug,其实这个异常在座的每个程序员几乎都遇到过,谁也不是一生下就是大牛,简单看了下代码,确实是多线程操作foreach,但并没有对foreach进行Add,Remove操作,扫完代码其实我也是有点懵,没撤只能调试了,在foreach里套一层trycatch,查看异常的线程堆栈从而找出了问题代码,代码简化如下:duz28资讯网——每日最新资讯28at.com

static void Main(string[] args)        {            var dict = new Dictionary<int, int>()            {                [1001] = 1,                [1002] = 10,                [1003] = 20            };            foreach (var userid in dict.Keys)            {                dict[userid] = dict[userid] + 1;            }        }

先寻找点安慰,说实话,凭肉眼你觉得这段代码会抛出异常吗?反正我是被骗过了,大写的尴尬,结论如下,运行一下便知。duz28资讯网——每日最新资讯28at.com

图片图片duz28资讯网——每日最新资讯28at.com

从图中看确实是异常,说明在foreach的过程中连迭代集合的 value 都不可以修改,这让我激起了强烈的探索欲,看看FCL中到底是怎么限制的。duz28资讯网——每日最新资讯28at.com

二、源码探索

1. 从IL中寻找答案

C#已发展到 9.0 了,到处都充斥着语法糖,有时候不看一下底层的IL都不知道到底是转化成了什么,所以这个是必须的。duz28资讯网——每日最新资讯28at.com

IL_000d: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, int32>::set_Item(!0, !1)    IL_001b: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, int32>::set_Item(!0, !1)    IL_0029: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, int32>::set_Item(!0, !1)    IL_0037: callvirt instance valuetype [System.Collections]System.Collections.Generic.Dictionary`2/KeyCollection/Enumerator<!0, !1> class [System.Collections]System.Collections.Generic.Dictionary`2/KeyCollection<int32, int32>::GetEnumerator()    .try    {        IL_003d: br.s IL_005a        // loop start (head: IL_005a)            IL_003f: ldloca.s 1            IL_0041: call instance !0 valuetype [System.Collections]System.Collections.Generic.Dictionary`2/KeyCollection/Enumerator<int32, int32>::get_Current()            IL_004c: callvirt instance !1 class [System.Collections]System.Collections.Generic.Dictionary`2<int32, int32>::get_Item(!0)            IL_0053: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, int32>::set_Item(!0, !1)            IL_005a: ldloca.s 1            IL_005c: call instance bool valuetype [System.Collections]System.Collections.Generic.Dictionary`2/KeyCollection/Enumerator<int32, int32>::MoveNext()            IL_0061: brtrue.s IL_003f        // end loop        IL_0063: leave.s IL_0074    } // end .try    finally    {    } // end handler

从IL代码中可以看到,先执行了三次字典的索引器操作,然后调用了 Dictionary.GetEnumerator 来生成字典的迭代类,这思路就非常清晰了,然后我们看一下类索引器都做了些什么。duz28资讯网——每日最新资讯28at.com

图片图片duz28资讯网——每日最新资讯28at.com

从图中可以看到,每一次的索引器操作,这里都执行了version++,所以字典初始化完成之后,这里的 versinotallow=3,没有问题吧,然后继续看代码,寻找 Dictionary.GetEnumerator 方法启动迭代类。duz28资讯网——每日最新资讯28at.com

图片图片duz28资讯网——每日最新资讯28at.com

上面代码的 _version = dictionary._version; 一定要看仔细了,在启动迭代类的时候记录了当时字典的版本号,也就是_versinotallow=3,然后继续探索moveNext方法干了什么,如下图:duz28资讯网——每日最新资讯28at.com

图片图片duz28资讯网——每日最新资讯28at.com

从图中可以看到,当每次执行moveNext的过程中,都会判断一下字典的 version 和 当初初始化迭代类中的version 版本号是否一致,如果不一致就抛出异常,所以这行代码就是点睛之笔了,当在foreach体中执行了 dict[userid] = dict[userid] + 1; 语句,相当于又执行了一次类索引器操作,这时候字典的version就变成 4 了,而当初初始化迭代类的时候还是3,自然下一次执行 moveNext 就是 3 != 4 抛出异常了。duz28资讯网——每日最新资讯28at.com

如果你非要让我证明给你看,这里可以使用dnspy直接调试源码,在异常那里下一个断点再查看两个version版本号不就知道啦。。。duz28资讯网——每日最新资讯28at.com

图片图片duz28资讯网——每日最新资讯28at.com

2. 面对疾风

有些朋友可能要说,码农今天分享的这篇一点水准都没有,我18年前就知道字典是不能动态修改的,还分析的头头是劲

本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-99653-0.htmlForeach 集合又抛经典异常了,这次一定要刨根问底

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 隐藏在前端工程师职责背后的那些事

下一篇: 中老铁路国际旅客列车开行满 1000 列次,累计发送旅客超 96 万人次

标签:
  • 热门焦点
  • 7月安卓手机性价比榜:努比亚+红魔两款新机入榜

    7月安卓手机性价比榜:努比亚+红魔两款新机入榜

    7月登场的新机有努比亚Z50S Pro和红魔8S Pro,除了三星之外目前唯二的两款搭载超频版骁龙8Gen2处理器的产品,而且努比亚和红魔也一贯有着不错的性价比,所以在本次的性价比榜单
  • 一加首款折叠屏!一加Open渲染图出炉:罕见单手可握小尺寸

    一加首款折叠屏!一加Open渲染图出炉:罕见单手可握小尺寸

    8月5日消息,此前就有爆料称,一加首款折叠屏手机将会在第三季度上市,如今随着时间临近,新机的各种消息也开始浮出水面。据悉,这款新机将会被命名为&ldquo;On
  • K6:面向开发人员的现代负载测试工具

    K6:面向开发人员的现代负载测试工具

    K6 是一个开源负载测试工具,可以轻松编写、运行和分析性能测试。它建立在 Go 和 JavaScript 之上,它被设计为功能强大、可扩展且易于使用。k6 可用于测试各种应用程序,包括 Web
  • 谷歌KDD'23工作:如何提升推荐系统Ranking模型训练稳定性

    谷歌KDD'23工作:如何提升推荐系统Ranking模型训练稳定性

    谷歌在KDD 2023发表了一篇工作,探索了推荐系统ranking模型的训练稳定性问题,分析了造成训练稳定性存在问题的潜在原因,以及现有的一些提升模型稳定性方法的不足,并提出了一种新
  • JVM优化:实战OutOfMemoryError异常

    JVM优化:实战OutOfMemoryError异常

    一、Java堆溢出堆内存中主要存放对象、数组等,只要不断地创建这些对象,并且保证 GC Roots 到对象之间有可达路径来避免垃 圾收集回收机制清除这些对象,当这些对象所占空间超过
  • 阿里大调整

    阿里大调整

    来源:产品刘有媒体报道称,近期淘宝天猫集团启动了近年来最大的人力制度改革,涉及员工绩效、层级体系等多个核心事项,目前已形成一个初步的&ldquo;征求意见版&rdquo;:1、取消P序列
  • 东方甄选单飞:有些鸟注定是关不住的

    东方甄选单飞:有些鸟注定是关不住的

    作者:彭宽鸿来源:华尔街科技眼&zwj;&zwj;&zwj;&zwj;&zwj;&zwj;&zwj;&zwj;&zwj;&zwj;东方甄选创始人俞敏洪带队的&ldquo;7天甘肃行&rdquo;直播活动已在近日顺利收官。成立后一
  • 华为Mate 60保护壳曝光:硕大后置相机模组 凸起程度有惊喜

    华为Mate 60保护壳曝光:硕大后置相机模组 凸起程度有惊喜

    这段时间以来,关于华为新旗舰的爆料日渐密集。据此前多方爆料,今年华为将开始恢复一年双旗舰战略,除上半年推出的P60系列外,往年下半年的Mate系列也将
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
Top
Baidu
map