1:背景1.讲故事昨天在StackOverflow上看到一个很有意思的问题,说:你会不会遍历字典,然后贴出各种奇奇怪怪的答案,很有意思,国庆节comingsoon好吧,我们玩得开心,我们来谈谈这种无聊的问题。二:使用foreach遍历为了方便演示,先测试代码:vardict=newDictionary(){[10]="A10",[20]="A20",[30]="A30",[40]="A40",[50]="A50"};1.直接foreachdict如果要讲百分比,估计50%+的朋友都用这种方法。为什么,简单粗暴,别的不说了,直接上代码:foreach(varitemindict){Console.WriteLine($"key={item.Key},value={item.Value}");}item这里是底层,在MoveNext的过程中用KeyValuePair包裹了出来,不信看源码:publicboolMoveNext(){while((uint)_index<(uint)_dictionary._count){refEntryreference=ref_dictionary._entries[_index++];if(reference.next>=-1){_current=newKeyValuePair(reference.key,reference.value);returntrue;}}}2.使用KeyPairValue刚才在foreach中解构也看到item是KeyValuePair类型,但是netcore对KeyValuePair进行了增强,增加了Deconstruct函数来解构KeyValuePair,代码如下:;publicTValueValue=>value;publicKeyValuePair(TKeykey,TValuevalue){this.key=key;this.value=value;}publicvoidDeconstruct(outTKeykey,outTValuevalue){key=Key;value=Value;}}通过这个解构函数,可以直接获取key,value,而不是打包的KeyValuePair,这在netframework中是不可接受的。实现代码如下:foreach((intkey,stringvalue)indict){Console.WriteLine($"key={key},value={value}");}3.foreachkeys前面的例子都是foreach直接上dict,其实也可以对dict进行foreach遍历。(varkeyindict.Keys){Console.WriteLine($"key={key},value={dict[key]}");}说到这里不知道大家有没有潜意识,就是dict可以只能通过foreach遍历,真相是这样的吗?为了寻找答案,我们回头看看foreach是如何遍历的publicstructEnumerator:IEnumerator>,IDisposable,IEnumerator,IDictionaryEnumerator{publicboolMoveNext(){while((uint)_index<(uint)_dictionary._count){refEntryreference=ref_dictionary._entries[_index++];if(reference.next>=-1){_current=newKeyValuePair(reference.key,reference.value);returntrue;}}_index=_dictionary._count+1;_current=default(KeyValuePair);returnfalse;}}仔细看看这个while循环,你应该明白它本质上是在遍历entries数组,所以while是在底层使用的,我可以用for代替循环dict吗?哈哈,反正就是仿的。三:用于遍历模拟MoveNext中的代码,重点是这句:refEntryreference=ref_dictionary._entries[_index++];,其实很简单,可以用Linq的ElementAt方法提取_entries数组的内容,也就是No~,修改后的代码如下:for(inti=0;i(thisIEnumerablesource,intindex){IListlist=sourceasIList;if(list!=null){returnlist[index];}if(index>=0){using(IEnumeratorenumerator=source.GetEnumerator()){while(enumerator.MoveNext()){if(index==0){returnenumerator.Current;}index--;}}}}从上面的代码可以看出,如果当前源码没有实现IList接口的话,就是一个巨大的坑。每次执行ElementAt方法,最坏的时间复杂度是O(N)。以刚才的for循环为例,它最坏的时间复杂度是O(n!),是不是比你想象的更可怕,教训是要多练习,看源码~4:总结一下,这篇文章列举了4种遍历dict的方式,不知道你会用到哪一种?需要注意的是,最后ElementAt一定要明白source判断的大坑。不要想当然地认为它是O(N)。嗯,更多遍历方法欢迎补充!