LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C#中容易被忽视的foreach

admin
2023年5月18日 15:19 本文热度 426

有句俗语:百姓日用而不知。我们c#程序员很喜欢,也非常习惯地用foreach。今天呢,我就带大家一起探索foreach,走,开始我们的旅程。

一、for语句用地好好的,为什么要提供一个foreach?

for (var i = 0; i < 10; i++) 
{    
//to do sth 
}
foreach (var n in list)
{    
//to do sth
}


首先,for循环,需要知道循环的次数,foreach不需要。其次,for循环在遍历对象的时候,略显麻烦,还需要通过下标索引找到当前对象,foreach不需要这么麻烦,显得更优雅。最后,for循环需要知道集合的细节,foreach不需要知道。

这一切的好处,得益于微软的封装,那我们看看foreach生成的IL代码:

IL_00a7:  callvirt   instance valuetype 
[System.Collections]System.Collections.Generic.List`1/Enumerator<!0>                        
class [System.Collections]System.Collections.Generic.List`1<int64>::GetEnumerator()
.try {   IL_00ae:  br.s       IL_00c9   IL_00b0:  ldloca.s   V_10   IL_00b2:  call       instance !0 valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int64>::get_Current()
  IL_00cb:  call       instance bool valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int64>::MoveNext()   IL_00d0:  brtrue.s   IL_00b0   IL_00d2:  leave.s    IL_00e3 }  // end .try finally {   IL_00d6:  constrained. valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int64>   IL_00dc:  callvirt   instance void [System.Runtime]System.IDisposable::Dispose()   IL_00e1:  nop   IL_00e2:  endfinally }   // end handlers

怎样的对象才能使用foreach呢?从微软的文档上看,实现了IEnumerable接口的对象,可以使用foreach,此接口只定义了一个方法:public System.Collections.IEnumerator GetEnumerator (); 有意思的是,它返回了一个IEnumerator接口,再看看这个接口:

有一个属性:Current两个方法MoveNext()、Reset(),现在我们回过头来看看生成的IL代码,真相大白。foreach只不过是个好吃的语法糖而已,编译器帮我们做好了一切。和直接写foreach类似的用法还有一个,就是对象的Foreach方法:

list.ForEach(n => { //to do sth }); 

那问题就来了,都是foreach,我该用哪个?

忍不住看看微软的源码:

internal void ForEach(Action<T> action)
{    
foreach (T x in this)    
{       
action(x);    
} 
} 

其实,就是定义了一个委托,我们把想要做的事情定义好,它来执行。这和直接使用foreach有何区别?我又忍不住好奇心,写了一段代码,比较了for和foreach的性能,先上结果:

说明下,最后一个是对象调用Foreach方法。数据反映的是随着数据规模下降,看运行时间有什么变化。从1亿次循环到1万次循环,耗时从几百毫秒到1毫秒以内。从图上,明显能看出性能差异,是从千万级别开始,for的性能最好,其次是对象的Foreach方法,最后是foreach。

for和foreach的性能差异,我们尚且能理解,但是对象的Foreach和直接foreach差异从何而来?我冥思苦想,百思不得其解。我试图从内存分配和垃圾回收的机制方向去理解,但是没有突破。我想着,直接foreach耗时,是不是因为,它多执行了什么东西,比如说多分配了一些变量,比如说,内存中这么大数据量,垃圾回收机制,不可能无动于衷,是不是垃圾回收机制导致的程序变慢,进而影响了性能。

我在循环完后,强行执行了一次GC,才释放了13.671875k,说明循环中,执行GC也没有什么意义,回收不了垃圾,但是如果循环中,频繁执行GC,确实会导致程序没法好好地运行。垃圾回收机制,会把不再引用的对象释放,而整个循环过程中,对象都在List中,所以GC应该不会运行。

那亲爱的程序员朋友,你觉得对象的Foreach方法和直接Foreach的性能差异,是怎么产生的呢,欢迎讨论,我把源码贴出来。

using System;using System.Collections.Generic;using System.Diagnostics;using System.Text;
namespace MyConsole.Test{    public class ForeachTest    {        public static void Test(long num)        {            Console.WriteLine("当前数据规模:" + num);
           DateTime start = DateTime.Now;
           for (var i = 0; i < num; i++)            {                var t = (i + 1) * 100 + 1;            }
           DateTime end = DateTime.Now;
           var costTime = end.Subtract(start).TotalMilliseconds;
           Console.WriteLine("for cost time:" + costTime + " ms");

           List<long> list = new List<long>();            for (var i = 0; i < num; i++)            {                list.Add(i);            }
           start = DateTime.Now;
           foreach (var n in list)            {                var t = (n + 1) * 100 + 1;            }
           end = DateTime.Now;
           costTime = end.Subtract(start).TotalMilliseconds;
           Console.WriteLine("foreach cost time:" + costTime + " ms");

           start = DateTime.Now;
           list.ForEach(n =>            {                var t = (n + 1) * 100 + 1;            });
           end = DateTime.Now;
           costTime = end.Subtract(start).TotalMilliseconds;
           Console.WriteLine("obj foreach cost time:" + costTime + " ms");
           Console.WriteLine("--------------------------------------------");            Console.WriteLine("");        }    }}

放到Main方法里:

long[] nums = {     100000000,     10000000,     1000000,     100000,     10000, };
foreach (int num in nums) {     for (int i = 0; i < 5; i++)     {         ForeachTest.Test(num);     } } Console.ReadLine();
 最后注意一点的是,foreach循环里面,不能随便添加或者删除元素,如果允许的话,程序将很难控制,而且非常容易出错,所以微软不允许这么干。

该文章在 2023/5/18 15:19:27 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved