foreach, 用还是不用,这是一个问题~

  接触过C#循环的朋友,想来对foreach应该不会陌生,相比一般的for循环方式,foreach显得更加优雅简洁,Unity支持C#脚本,平日使用中数组列表什么的自然也会遇到不少,想来foreach定然大有用武之地呀!

  可惜网上大家的共识却是:不要用foreach!

  WTF?

  原因其实也简单,就是为了避免GC,因为foreach会“偷偷”申请内存,使用过度的话自然会引发系统的垃圾收集!

  有鉴于此,建议大家平日尽量限制使用foreach,转而使用for之类的循环控制语法,尤其注意一下Update(或者说频繁调用的函数)中的foreach使用,不小心的话确实会导致频繁GC~

  OK,基础知识普及完毕,接下来让我们再细致看下(基于Unity5.3.3f1):  

  1. foreach真的会申请内存吗?申请多少内存?(或者叫GC Alloc)

  简单写个测试,Profiler一下就明了了~

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ForeachTest : MonoBehaviour 
{
    int[] m_array = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    ArrayList m_arrayList = new ArrayList { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    List<int> m_list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    void Update()
    {
        ForeachList();
    }

    void ForeachArray()
    {
        // foreach array
        foreach (var element in m_array)
        {
            Func(element);
        }
    }

    void ForeachArrayList()
    {
        // foreach array list
        foreach (var element in m_arrayList)
        {
            Func((int)element);
        }
    }

    void ForeachList()
    {
        // foreach list
        foreach (var element in m_list)
        {
            Func(element);
        }
    }

    void Func(int element)
    {
    }
	
}

  可以看到,foreach一个List确实会产生内存申请,大小为40字节~

  2. 为什么foreach会申请内存呢?

  说到这个问题,我们便需要进一步的认识一下foreach了,相比传统的for,foreach其实是C#的一种语法糖,还拿上面的测试程序举例,foreach一个List最后会被C#翻译为大概下面这种形式:

using (List<int>.Enumerator enumerator = list.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        int current = enumerator.Current;
        this.Func(current);
    }
}

  初看上去似乎没有什么申请内存的地方,但是注意到这里using的使用,其最后会通过IDisposable接口调用Dispose,但是由于List的Enumerator是个值类型,转换为IDisposable接口会导致装箱操作,继而便引发了内存申请~

  使用ILSpy看下生成的IL便更加一目了然了:

  3. foreach List会触发GC Alloc,那么其他类型的列表类型是不是一样呢?

   首先看下原生数组:

void ForeachArray()
{
    // foreach array
    foreach (var element in m_array)
    {
        Func(element);
    }
}

  竟然没有产生GC Alloc?看下转换后的代码:

// ForeachTest
private void ForeachArray()
{
	int[] array = this.m_array;
	for (int i = 0; i < array.Length; i++)
	{
		int element = array[i];
		this.Func(element);
	}
}

  看来C#对于原生数组的foreach形式做了优化,使用了传统的for来遍历数组,自然便不会申请额外的内存了~

  再来试下ArrayList~

void ForeachArrayList()
{
	// foreach array list
	foreach (var element in m_arrayList)
	{
		Func((int)element);
	}
}

  看来同List一样,也会产生40字节的GC Alloc,同样的看下转换后的代码:

// ForeachTest
private void ForeachArrayList()
{
	using (IEnumerator enumerator = this.m_arrayList.GetEnumerator())
	{
		while (enumerator.MoveNext())
		{
			object current = enumerator.get_Current();
			this.Func((int)current);
		}
	}
}

  形式上与foreach List如出一辙,但是值得指出的是,这里产生内存申请的地方与foreach List是不同的,foreach List如上面所说,是由于装箱操作而引起的GC Alloc,但是foreach ArrayList则是由于GetEnumerator,因为ArrayList的Enumerator 是引用类型,创建时自然会在堆上分配(也就是产生了内存分配),后面虽然也会尝试转换为IDisposable接口来调用Dispose,但是因为是引用类型间的转换,并不会引发Box~

  IL代码最能说明问题:

  4. 真的不能再使用foreach了吗?

  诚然,foreach会产生内存申请,但是相对而言GC Alloc的大小还是相对有限的(上面看到是40B),所以只要不是频繁调用,这点消耗还是能够接受的;再者,如果你使用的是原生数组,那么便不用担心了,随意使用foreach即可,因为就像上面看到的那样,foreach原生数组并不会产生GC Alloc;最后,其实新版的C#早已修复了foreach会产生额外内存申请的问题,只是由于Unity内含的Mono版本较早,没有修复该问题罢了,如果你想痛快的在Unity中使用foreach,可以看看这里这里~

  OK,没想简单的一个foreach也讲了这么多东西,其中的知识其实网上早已有了很多优秀的解释,知乎上的一篇相关问答想来应该是个不错的起点,有兴趣的朋友可以仔细看看~

  好了,下次再见吧~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xingoo, 一个梦想做发明家的程序员

剑指OFFER之最小的K个数(九度OJ1371)

题目描述: 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 输入: 每个测试案例包括...

2029
来自专栏我杨某人的青春满是悔恨

Swift API 设计指南(上)

本文翻译自苹果官方文档:Swift API Design Guidelines,如有错漏,欢迎指出。

1133
来自专栏landv

C语言介绍

4262
来自专栏斑斓

编程修炼 | Scala亮瞎Java的眼(二)

继续上一期的话题,介绍Scala有别于Java的特性。说些题外话,当我推荐Scala时,提出质疑最多的往往不是Java程序员,而是负责团队的管理者,尤其是略懂技...

3725
来自专栏王亚昌的专栏

C++多线程编程学习二 [类中封装互斥量的设计]

      之前我也提到过,如果一个类的数据成员中在多线程环境中可能会被竞争使用时,一定要在类中解决这个问题,而不是在代码编写过程中在每次使用时去申请或释放,这...

821
来自专栏Crossin的编程教室

这些年,你们一起踩过的坑(2)

上次我们踩坑总结文章 这些年,你们一起踩过的坑(1) 受到了不少同学的认可。我也确信文中所涉及的问题是非常具有普遍性的,对绝大多数初学者都会有帮助。

1163
来自专栏magicsoar

Effective Modern C++翻译(1):序言

/*********************************************************** 关于书: 书是我从网上找到的effec...

1969
来自专栏黑泽君的专栏

HashMap与Hashtable的区别是面试中经常遇到的一个问题。

HashMap与Hashtable的区别是面试中经常遇到的一个问题。这个问题看似简单,但如果深究进去,也能了解到不少知识。本文对两者从来源,特性,算法等多个方面...

2443
来自专栏顶级程序员

Python 工匠:善用变量来改善代码质量

我一直觉得编程某种意义上是一门『手艺』,因为优雅而高效的代码,就如同完美的手工艺品一样让人赏心悦目。

1223
来自专栏编程

手把手教你半个小时用python语言编程出你的第一个程序

学习目标 知道有序的软件开发过程的步骤。 了解遵循输入、处理、输出(IPO)模式的程序,并能够以简单的方式修改它们。 了解构成有效Python标识符和表达式的规...

3905

扫码关注云+社区

领取腾讯云代金券