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

當前位置:首頁 > 科技  > 軟件

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

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

一、背景

1. 講故事

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

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

圖片圖片L9h28資訊網——每日最新資訊28at.com

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

二、源碼探索

1. 從IL中尋找答案

C#已發展到 9.0 了,到處都充斥著語法糖,有時候不看一下底層的IL都不知道到底是轉化成了什么,所以這個是必須的。L9h28資訊網——每日最新資訊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 來生成字典的迭代類,這思路就非常清晰了,然后我們看一下類索引器都做了些什么。L9h28資訊網——每日最新資訊28at.com

圖片圖片L9h28資訊網——每日最新資訊28at.com

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

圖片圖片L9h28資訊網——每日最新資訊28at.com

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

圖片圖片L9h28資訊網——每日最新資訊28at.com

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

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

圖片圖片L9h28資訊網——每日最新資訊28at.com

2. 面對疾風

有些朋友可能要說,碼農今天分享的這篇一點水準都沒有,我18年前就知道字典是不能動態修改的,還分析的頭頭是勁

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

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

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

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

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 彭泽县| 湖南省| 重庆市| 兴仁县| 大宁县| 二连浩特市| 富民县| 湛江市| 漠河县| 奇台县| 平阳县| 镇安县| 石狮市| 贵德县| 托克逊县| 青州市| 高雄县| 元朗区| 临城县| 舞阳县| 西宁市| 景东| 察隅县| 晋城| 桃园市| 花莲县| 凤庆县| 陇西县| 大荔县| 济阳县| 五莲县| 运城市| 托克逊县| 三台县| 滁州市| 闽侯县| 错那县| 台前县| 武山县| 武汉市| 陆丰市|