有句俗语:百姓日用而不知。我们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循环里面,不能随便添加或者删除元素,如果允许的话,程序将很难控制,而且非常容易出错,所以微软不允许这么干。

最新文章

  1. android 事件分发机制详解(OnTouchListener,OnClick)
  2. AOP的实现原理
  3. winform设置文本框宽度 根据文字数量和字体返回宽度
  4. Eclipse使用快捷键代码格式化有时失效解决办法
  5. VMware安装虚拟系统问题
  6. css hack 整理
  7. 变量监控 指令 gt-wach
  8. MySQL存储过程循环添加数据
  9. php无缝连接滚动
  10. 关于bootStrapdialog 学习心得
  11. Vuex原来可以这样上手
  12. VisualStudio2017下ASP.NET CORE的TagHelper智能提示解决办法
  13. java文件的读写操作
  14. jQuery-&gt;JavaScript一览表
  15. [Java] 在 jar 文件中读取 resources 目录下的文件
  16. FineReport父子格实现动态参数注入
  17. LINUX下printf输出字体的特效
  18. 系统的讲解 - SSO单点登录
  19. .NET Core整理之配置EFCore
  20. CentOS 使用firewalld打开防火墙与端口

热门文章

  1. Node.js精进(6)——文件
  2. 宝塔Linux面板安装教程
  3. Spring Boot:整合knife4j
  4. Vue3 项目实战
  5. JavaWeb的技术体系
  6. Nginx越界读取缓存漏洞 CVE-2017-7529
  7. WannaRen来袭:螣龙安科带你盘点那些年的勒索病毒
  8. Solution -「校内题」Xorequ
  9. 电子表格分两级:Excel和WPS是一级,未来5年,75%的人将用二级
  10. 强化版按键消抖Verilog实现