C#匿名函数与闭包
起因
最近看到了一段C#程序,一开始就简单的以为只是一个for循环的0~9的打印,但是深入了解之后发现我错了,而且对其中的原理相知甚少。代码如下:
1 | public static void Main(string[] args) |
这段代码的运行结果输出是10个10,如果做对了,想必已经是了解其中的原理,下面的内容可以不用看了。
原因
原因其实很简单,使用编辑器查看IL代码,可以很清晰的看到:编译器为匿名函数生成了一个名为<>c__DisplayClass0_0的类,并把int i作为了类内的成员变量,并在类中生成了
闭包中存在的问题
既然匿名函数在使用时在堆上new了一个实例,分配了内存,所以必定涉及到了GC。那么代码中的哪种写法导会有严重的GC问题,哪种写法又能尽量避免GC。搜索之后,发现猫仙人的这篇文章总结得非常到位:文章传送连接,以下是我对他文章结论的进一步总结,具体论证细节可以点击连接去查看。
捕获变量的分类
外部变量的分类重要分为3种:
1、捕获静态变量
2、捕获实例字段
3、捕获外部方法的局部变量
其中分配内存的多少为:3>2>1>无闭包,并且捕获外部方法的局部变量的内存分配量高达2n次,为了记录外部局部变量的值,编译器每次都new了一个新的匿名类实例和Action实例。
优化方法
文章中指出了,可以通过增加参数数量,使用方法参数去传递要捕获的变量,避免掉对外部变量的捕获,同时避免闭包的额外内存分配。对于该文章开头的代码,若想改成输出0~9,就有两种办法,以下给出代码截图。第二种用了优化建议,内存性能要优于第一种。
捕获外部方法的局部变量
编译器new了n个匿名类实例和Action实例
增加参数变量
编译器只new对应的Aciton实例
总结
对于以上验证,可以简单粗暴的认为:runtime直接不使用()=>{}这种匿名函数的写法,能非常有效的避免额外内存开销。




