ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底

admin 2个月前 (06-15) 科技 22 0

一:靠山

1. 讲故事

最近同事在写一段营业逻辑的时刻,程序跑起来总是报:聚集已修改;可能无法执行枚举操作,硬是没有找到什么情况下会导致这个异常发生,就让我来找一下bug,实在这个异常在座的每个程序员险些都遇到过,谁也不是一生下就是大牛,简朴看了下代码,确实是多线程操作foreach,但并没有对foreach举行Add,Remove操作,扫完代码实在我也是有点懵,没撤只能调试了,在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;
            }
        }

先寻找点抚慰,说实话,凭肉眼你以为这段代码会抛出异常吗? 横竖我是上当过了,大写的尴尬,结论如下,运行一下便知。

从图中看确实是异常,说明在foreach的过程中连迭代聚集的 value 都不可以修改,这让我激起了强烈的探索欲,看看FCL中到底是怎么限制的。

二:源码探索

1. 从IL中寻找谜底

C#已发展到 9.0 了,到处都充斥着语法糖,有时刻不看一下底层的IL都不知道到底是转化成了什么,以是这个是必须的。


	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 来天生字典的迭代类,这思绪就异常清晰了,然后我们看一下类索引器都做了些什么。

从图中可以看到,每一次的索引器操作,这里都执行了version++,以是字典初始化完成之后,这里的 version=3,没有问题吧,然后继续看代码,寻找 Dictionary.GetEnumerator 方式启动迭代类。

上面代码的 _version = dictionary._version; 一定要看仔细了,在启动迭代类的时刻纪录了那时字典的版本号,也就是_version=3,然后继续探索moveNext方式干了什么,如下图:

ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底 第1张

从图中可以看到,当每次执行moveNext的过程中,都市判断一下字典的 version 和 当初初始化迭代类中的version 版本号是否一致,若是不一致就抛出异常,以是这行代码就是点睛之笔了,当在foreach体中执行了 dict[userid] = dict[userid] + 1; 语句,相当于又执行了一次类索引器操作,这时刻字典的version就酿成 4 了,而当初初始化迭代类的时刻照样3,自然下一次执行 moveNext 就是 3 != 4 抛出异常了。

若是你非要让我证实给你看,这里可以使用dnspy直接调试源码,在异常那里下一个断点再查看两个version版本号不就知道啦。。。

ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底 第2张

2. 面临疾风

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

然则我有话要说,这个还确实是我的一个盲区,平时在迭代字典的时刻value一样平常都是引用类型,动态修改引用类型的值自然是没有问题的,这是由于你不管怎么修改都不会改变 _version 版本号,但质疑我的也不要把话说的太满,由于这种操作是异常语义化异常民众的需求,你能保证后面net版本不支持这个吗??? 若是你说不可能,那恭喜你,被我带到坑内里去啦。

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

ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底 第3张

惊讶吧, 居然在 .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

ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底 第4张

  • net5

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

ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底 第5张

对比两张图你会发现 .Net5 中并没有做 _version++ 操作,这就了,若是你再细读代码,你还发现 .Net5 对字典举行了较大幅度的优化,哈哈,当初在 .Net5 之前发生的错误,在 .Net5 中居然没有啦!

四: 总结

源码眼前,不谈隐私,没事多翻翻源码,有可能另有意外收获,比如在 .Net 5下的这点新发现,可能照样全网第一个哦,这要是两个大牛争吵,让小白去信赖谁呢,嘿嘿,源码才是真正的专家~

如您有更多问题与我互动,扫描下方进来吧~

ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底 第6张,

环球UG

欢迎进入环球UG官网(UG环球):www.ugbet.us,环球UG官方网站:www.ugbet.net开放环球UG网址访问、环球UG会员注册、环球UG代理申请、环球UG电脑客户端、环球UG手机版下载等业务。

皇冠体育声明:该文看法仅代表作者自己,与本平台无关。转载请注明:ug环球手机版下载:foreach 聚集又抛经典异常了,这次一定要刨根问底

网友评论

  • (*)

最新评论

站点信息

  • 文章总数:639
  • 页面总数:0
  • 分类总数:8
  • 标签总数:975
  • 评论总数:250
  • 浏览总数:6928