C# 深度理解 for 和 foreach

1 前言
很多人认为,在C#中 for 和 foreach 功能是一样的,foreach 顶多就是比 for 要更方便一些。但是实际上真的是这样吗?在本文中,让我们通过一个实例来理解其底层的工作原理。

2 for VS foreach
首先,请看下面的代码段:

List<Person> people = new List<Person>();
for(int i = 0; i < 100; i++){
    var p = people[i];
    // TODO: 下面的代码处理 p
    
}
List<Person> people = new List<Person>();
foreach(var p in people)
    // TODO: 下面的代码处理p
}

我们可以看到,在 TODO 下方,都是直接使用 p 就可以完成相关操作。两者从使用上来说,foreach 能够比 for 省去一个赋值语句(),也就仅此而已,两者感觉完全是一样的,但是实际上真的是这样吗?

3 一个示例
在回答问题之前,让我们再看这样的一个示例。以下代码先定义了一个列表 List<int> list并添加3亿个整型数,然后分别使用 for 和 foreach 进行累加求和,结果分别保存在 sum1 和 sum2 中,并对这两种方法进行计时,在最后输出计算结果和所用时间。

static void Main(string[] args)
{
     Console.WriteLine("processing...");
     int size = 300_000_000;
     List<int> list = new List<int>(size);
     for (int i = 0; i < size; i++)
         list.Add(i);

    // 使用 for 求和
     var t1 = DateTime.Now;
     double sum1 = 0;
     for (int i = 0; i < list.Count; i++)
         sum1 += list[i];
     var t2 = DateTime.Now;
     
     // 使用 foreach 求和
     double sum2 = 0;
     foreach (var v in list)
         sum2 += v;
     var t3 = DateTime.Now;
     
     // 输出结果
     Console.WriteLine($"done.\nResult: sum1={sum1}, sum2={sum2}");
     Console.WriteLine($"time1: {t2 - t1}\ntime2: {t3 - t2}");
}



输出如下:

processing...
done.
Result: sum1=44999999767108860, sum2=44999999767108860
time1: 00:00:01.1345446
time2: 00:00:00.6056811



4 原理分析
通过结果分析,我们可以看出,使用 for 循环与 foreach 的用时相差近1倍,通过多次测试也基本是这个结果。这个时间差显然不是误差,根本原因就在于两者实现的方式不同:

for 对 list 中的元素逐一进行访问,由于 list[i] 是列表,所以每次在访问 list[i] 时,需要重新定位,因此要消耗很多的定位时间;
foreach 则是只对 list 进行一次遍历,从第1个元素开始直到最后一个元素,具体在实现中使用 yield return 来实现(可以参见之前我的文章 深入理解C#中yield return的用法 )。
所以,foreach 是为可迭代的对象(iteratable)专门设计的,能够只遍历一次的情况下,完成对有元素的访问。

明白了这个原理,我们可以将 list 换成数组,再测试一次,代码和结果如下:       

static void Main(string[] args)
        {
            //Test1();
            //Test2();
             Thread.Sleep(4000);
            Console.WriteLine("processing...");
            int size = 300_000_000;
            int[] arr = new int[size];
            for (int i = 0; i < size; i++)
                arr[i] = i;

            var t1 = DateTime.Now;
            double sum1 = 0;
            for (int i = 0; i < arr.Length; i++)
                sum1 += arr[i];
            var t2 = DateTime.Now;
            double sum2 = 0;
            foreach (var v in arr)
                sum2 += v;
            var t3 = DateTime.Now;
            Console.WriteLine($"done.\nResult: sum1={sum1}, sum2={sum2}");
            Console.WriteLine($"time1: {t2 - t1}\ntime2: {t3 - t2}");
        }


结果

processing...
done.
Result: sum1=44999999767108860, sum2=44999999767108860
time1: 00:00:00.6329427
time2: 00:00:00.6343659



由于数组有较好的随机访问性能,所以两者的结果基本一样。如果再换一种数据类型,比如 LinkedList,由于其随机读写性能较 List 更差,所以两者的时间差会更大,即 foreach 的优势更明显,有兴趣的读者可以自行尝试一下。

5 总结
C#中的 for 和 foreach 的设计目的是不一样的,for 是一般性的循环,而 foreach 是专门用于可以迭代的集合的循环方法,能够有效地减少访问次数,从而达到优化的效果。因此,在遍历随机访问性能的集合时,两者区别不大,而随机访问性能差时,优先使用foreach 会取得更好的性能。
————————————————
版权声明:本文为CSDN博主「郝伟博士」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43145361/article/details/125298208

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值