日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

Foreach 集合又拋經(jīng)典異常了,這次一定要刨根問底

來源: 責(zé)編: 時(shí)間:2024-07-09 07:04:57 154觀看
導(dǎo)讀一、背景1. 講故事最近同事在寫一段業(yè)務(wù)邏輯的時(shí)候,程序跑起來總是報(bào):集合已修改;可能無法執(zhí)行枚舉操作,硬是沒有找到什么情況下會導(dǎo)致這個(gè)異常產(chǎn)生,就讓我來找一下bug,其實(shí)這個(gè)異常在座的每個(gè)程序員幾乎都遇到過,誰也不是一

一、背景

1. 講故事

最近同事在寫一段業(yè)務(wù)邏輯的時(shí)候,程序跑起來總是報(bào):集合已修改;可能無法執(zhí)行枚舉操作,硬是沒有找到什么情況下會導(dǎo)致這個(gè)異常產(chǎn)生,就讓我來找一下bug,其實(shí)這個(gè)異常在座的每個(gè)程序員幾乎都遇到過,誰也不是一生下就是大牛,簡單看了下代碼,確實(shí)是多線程操作foreach,但并沒有對foreach進(jìn)行Add,Remove操作,掃完代碼其實(shí)我也是有點(diǎn)懵,沒撤只能調(diào)試了,在foreach里套一層trycatch,查看異常的線程堆棧從而找出了問題代碼,代碼簡化如下:z9Z28資訊網(wǎng)——每日最新資訊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;            }        }

先尋找點(diǎn)安慰,說實(shí)話,憑肉眼你覺得這段代碼會拋出異常嗎?反正我是被騙過了,大寫的尷尬,結(jié)論如下,運(yùn)行一下便知。z9Z28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片z9Z28資訊網(wǎng)——每日最新資訊28at.com

從圖中看確實(shí)是異常,說明在foreach的過程中連迭代集合的 value 都不可以修改,這讓我激起了強(qiáng)烈的探索欲,看看FCL中到底是怎么限制的。z9Z28資訊網(wǎng)——每日最新資訊28at.com

二、源碼探索

1. 從IL中尋找答案

C#已發(fā)展到 9.0 了,到處都充斥著語法糖,有時(shí)候不看一下底層的IL都不知道到底是轉(zhuǎn)化成了什么,所以這個(gè)是必須的。z9Z28資訊網(wǎng)——每日最新資訊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代碼中可以看到,先執(zhí)行了三次字典的索引器操作,然后調(diào)用了 Dictionary.GetEnumerator 來生成字典的迭代類,這思路就非常清晰了,然后我們看一下類索引器都做了些什么。z9Z28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片z9Z28資訊網(wǎng)——每日最新資訊28at.com

從圖中可以看到,每一次的索引器操作,這里都執(zhí)行了version++,所以字典初始化完成之后,這里的 versinotallow=3,沒有問題吧,然后繼續(xù)看代碼,尋找 Dictionary.GetEnumerator 方法啟動迭代類。z9Z28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片z9Z28資訊網(wǎng)——每日最新資訊28at.com

上面代碼的 _version = dictionary._version; 一定要看仔細(xì)了,在啟動迭代類的時(shí)候記錄了當(dāng)時(shí)字典的版本號,也就是_versinotallow=3,然后繼續(xù)探索moveNext方法干了什么,如下圖:z9Z28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片z9Z28資訊網(wǎng)——每日最新資訊28at.com

從圖中可以看到,當(dāng)每次執(zhí)行moveNext的過程中,都會判斷一下字典的 version 和 當(dāng)初初始化迭代類中的version 版本號是否一致,如果不一致就拋出異常,所以這行代碼就是點(diǎn)睛之筆了,當(dāng)在foreach體中執(zhí)行了 dict[userid] = dict[userid] + 1; 語句,相當(dāng)于又執(zhí)行了一次類索引器操作,這時(shí)候字典的version就變成 4 了,而當(dāng)初初始化迭代類的時(shí)候還是3,自然下一次執(zhí)行 moveNext 就是 3 != 4 拋出異常了。z9Z28資訊網(wǎng)——每日最新資訊28at.com

如果你非要讓我證明給你看,這里可以使用dnspy直接調(diào)試源碼,在異常那里下一個(gè)斷點(diǎn)再查看兩個(gè)version版本號不就知道啦。。。z9Z28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片z9Z28資訊網(wǎng)——每日最新資訊28at.com

2. 面對疾風(fēng)

有些朋友可能要說,碼農(nóng)今天分享的這篇一點(diǎn)水準(zhǔn)都沒有,我18年前就知道字典是不能動態(tài)修改的,還分析的頭頭是勁

本文鏈接:http://www.www897cc.com/showinfo-26-99653-0.htmlForeach 集合又拋經(jīng)典異常了,這次一定要刨根問底

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: 隱藏在前端工程師職責(zé)背后的那些事

下一篇: 中老鐵路國際旅客列車開行滿 1000 列次,累計(jì)發(fā)送旅客超 96 萬人次

標(biāo)簽:
  • 熱門焦點(diǎn)
Top 主站蜘蛛池模板: 广德县| 乌拉特后旗| 沁水县| 盐池县| 汨罗市| 天台县| 塔城市| 富宁县| 岫岩| 嘉兴市| 饶平县| 新乐市| 恩施市| 仙桃市| 泗阳县| 密山市| 红河县| 贺州市| 南宫市| 育儿| 大邑县| 兴城市| 堆龙德庆县| 田林县| 河津市| 库尔勒市| 全州县| 米林县| 县级市| 朔州市| 永康市| 栖霞市| 张家界市| 察哈| 镇江市| 荆门市| 孟连| 上栗县| 永州市| 根河市| 河池市|