成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發(fā) 前端
C#已發(fā)展到 9.0 了,到處都充斥著語法糖,有時候不看一下底層的IL都不知道到底是轉(zhuǎn)化成了什么,所以這個是必須的。

一、背景

1. 講故事

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

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;
            }
        }

先尋找點安慰,說實話,憑肉眼你覺得這段代碼會拋出異常嗎?反正我是被騙過了,大寫的尷尬,結(jié)論如下,運行一下便知。

圖片圖片

從圖中看確實是異常,說明在foreach的過程中連迭代集合的 value 都不可以修改,這讓我激起了強烈的探索欲,看看FCL中到底是怎么限制的。

二、源碼探索

1. 從IL中尋找答案

C#已發(fā)展到 9.0 了,到處都充斥著語法糖,有時候不看一下底層的IL都不知道到底是轉(zhuǎn)化成了什么,所以這個是必須的。

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 來生成字典的迭代類,這思路就非常清晰了,然后我們看一下類索引器都做了些什么。

圖片圖片

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

圖片圖片

上面代碼的 _version = dictionary._version; 一定要看仔細(xì)了,在啟動迭代類的時候記錄了當(dāng)時字典的版本號,也就是_versinotallow=3,然后繼續(xù)探索moveNext方法干了什么,如下圖:

圖片圖片

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

如果你非要讓我證明給你看,這里可以使用dnspy直接調(diào)試源碼,在異常那里下一個斷點再查看兩個version版本號不就知道啦。。。

圖片圖片

2. 面對疾風(fēng)

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

但是我有話要說,這個還確實是我的一個盲區(qū),平時在迭代字典的時候value一般都是引用類型,動態(tài)修改引用類型的值自然是沒有問題的,這是因為你不管怎么修改都不會改變 _version 版本號,但質(zhì)疑我的也不要把話說的太滿,因為這種操作是非常語義化非常大眾的需求,你能保證后面net版本不支持這個嗎??? 如果你說不可能,那恭喜你,被我?guī)У娇永锩嫒ダ病??????

下面我用原封不動的代碼在 .net 5 下跑一次,睜大眼睛好好看哦~~~

圖片圖片

驚訝吧, 居然在 .Net 5 中可以的,接下來用ILSpy去查查底層源碼,.netcore 3.1 和 net5 中分別對 類索引器 都做了啥修改。

  • netcore 3.1

Path:C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.2\System.Private.CoreLib.dll

圖片圖片

  • net5

Path:C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.0-preview.5.20278.1\System.Private.CoreLib.dll

圖片

對比兩張圖你會發(fā)現(xiàn) .Net5 中并沒有做 _version++ 操作,這就????了,如果你再細(xì)讀代碼,你還發(fā)現(xiàn) .Net5 對字典進行了較大幅度的優(yōu)化,哈哈,當(dāng)初在 .Net5 之前產(chǎn)生的錯誤,在 .Net5 中居然沒有啦!

四、總結(jié)

源碼面前,不談隱私,沒事多翻翻源碼,有可能還有意外收獲,比如在 .Net 5下的這點新發(fā)現(xiàn),可能還是全網(wǎng)第一個哦,這要是兩個大牛爭吵,讓小白去相信誰呢,嘿嘿,源碼才是真正的專家!

責(zé)任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2019-07-04 10:49:13

HTTPWebSocket協(xié)議

2015-07-02 15:04:53

CSS好奇心+

2022-04-20 11:41:45

Kafka數(shù)據(jù)解決方案

2013-10-10 15:41:38

綠色數(shù)據(jù)中心數(shù)據(jù)中心

2012-09-07 09:23:01

Win 8操作系統(tǒng)

2010-03-22 16:51:31

無線網(wǎng)絡(luò)穩(wěn)定性

2023-02-07 08:36:32

2021-02-09 08:13:51

項目內(nèi)存TCP

2020-11-13 07:14:55

Kafka消息中間件

2019-08-09 11:25:01

Java虛擬機Java程序員

2022-12-06 09:10:56

KVC原理數(shù)據(jù)篩選

2021-04-27 22:32:18

Python

2019-01-17 09:14:34

2024-05-10 08:10:05

Spring虛擬線程JDK

2020-11-23 18:36:21

容器微服務(wù)程序

2010-11-19 16:02:42

IT族

2025-02-05 14:28:19

2020-04-09 13:38:40

MySQL數(shù)據(jù)庫臟讀

2011-11-09 14:54:50

Linux操作系統(tǒng)

2022-05-30 07:36:07

Python腳本函數(shù)
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 一区二区三区 在线 | 国产免费xxx | 国产在线www | 一区二区三区四区不卡视频 | 成人精品鲁一区一区二区 | 看片网站在线 | 九九久久久 | 成人午夜免费福利视频 | 美女视频黄色的 | 国产精品视频中文字幕 | 国产精品精品 | 午夜一区二区三区在线观看 | 91资源在线| 国产女人叫床高潮大片免费 | 久久一区二区视频 | 欧美精品中文字幕久久二区 | 久草新在线 | 最新国产视频 | 亚洲一区二区视频在线播放 | 免费看国产一级特黄aaaa大片 | 成人在线中文字幕 | 午夜视频免费在线观看 | 日韩成人在线播放 | 国产精品美女www爽爽爽 | 一区二区三区四区在线 | 国产一区二区麻豆 | 精品成人在线 | 国产美女精品 | 欧美在线国产精品 | 超碰婷婷 | 国产高清在线视频 | 国产精品久久久久久久久久久免费看 | 天天干免费视频 | 亚洲精品视频一区二区三区 | 国产激情一区二区三区 | 日韩午夜影院 | 午夜三区 | 午夜在线| 国产羞羞视频在线观看 | 国产精品亚洲一区二区三区在线 | 国产一区2区 |