闭包是什么?之前看面试题的时候才发现这个名词。html
闭包在实际项目中会有什么问题?如今就让咱们一块儿来看下这个不太熟悉的名词。面试
若是在实际工做中用到了匿名函数和lamada表达式,那你就应该高度注意啦.编程
请问下你们这段代码的输出结果是什么样的呢?闭包
public static void Main() { Console.WriteLine("Starting."); for (int i = 0; i < 4; ++i) Task.Run(() => Console.WriteLine(i)); Console.WriteLine("Finished. Press <ENTER> to exit."); Console.ReadLine(); }
输出结果:ide
Starting. Finished. Press <ENTER> to exit. 4 4 4 4
你答对了吗?若是没有请跟随我一块儿来看下这里的深层缘由。函数
public static void Main() { Console.WriteLine("Starting."); for (int i = 0; i < 4; ++i) { int j = i; Task.Run(() => Console.WriteLine(j)); } Console.WriteLine("Finished. Press <ENTER> to exit."); Console.ReadLine(); }
输出结果oop
Starting. Finished. Press <ENTER> to exit. 0 1 3 2
using System; class Test { static void Main() { Action action = CreateAction(); action(); action(); } static Action CreateAction() { int counter = 0; return delegate { // Yes, it could be done in one statement; // but it is clearer like this. counter++; Console.WriteLine("counter={0}", counter); }; } }
输出this
counter=1 counter=2
In essence, a closure is a block of code which can be executed at a later time, but which maintains the environment in which it was first created - i.e. it can still use the local variables etc of the method which created it, even after that method has finished executing.spa
这段话的大意是:从本质上说,闭包是一段能够在晚些时候执行的代码块,可是这段代码块依然维护着它第一个被建立时环境(执行上下文)- 即它仍可使用建立它的方法中局部变量,即便那个方法已经执行完了。code
这段话准确地来讲不能算做定义,但形象的给出了描述。这里就不给出绝对定义啦。wiki上有这方面的描述。
C#中一般经过匿名函数和lamada表达式来实现闭包。
var values = new List<int> { 100, 110, 120 }; var funcs = new List<Func<int>>(); foreach (var v in values) funcs.Add(() => { //Console.WriteLine(v); return v; }); foreach (var f in funcs) Console.WriteLine(f()); Console.WriteLine("{0}{0}", Environment.NewLine); funcs.Clear(); for (var i = 0; i < values.Count; i++) { //var v2 = values[i];
funcs.Add(() => { var v2 = values[i]; //will throw exception
return v2; }); } foreach (var f in funcs) Console.WriteLine(f());
Because ()=>v means "return the current value of variable v", not "return the value v was back when the delegate was created",Closures close over variables, not over values.
原文大意:由于() = > v "返回变量 v 的当前值",而不是建立该委托时"v“ 的返回值 。闭包”变量“,而不是闭包”值“。
因此在”for“循环中的添加的匿名函数,只是返回了变量i 而不是i的值。因此知道f() 被真正执行时,i已是values.Count 值啦,因此会抛出”超出索引范围“。那为啥foreach 没事呢?那就让咱们接着看下闭包的来头。
The latter. The C# 1.0 specification actually did not say whether the loop variable was inside or outside the loop body, as it made no observable difference. When closure semantics were introduced in C# 2.0, the choice was made to put the loop variable outside the loop, consistent with the "for" loop.--Eric Lippert
闭包在C#2.0 的时候引入了闭包语法,选择将循环变量放在循环体外面,for 和foreach 在这方面处理都是一致的。但随着人们在使用过程当中的种种不适,微软作出了”一点“让步,在C#5 中对”foreach“作了调整,但对”for“没有作改动。具体改动以下说:
We are taking the breaking change. In C# 5, the loop variable of a foreach will be logically inside the loop, and therefore closures will close over a fresh copy of the variable each time. The "for" loop will not be changed. --Eric Lippert
原文大意:在C#5中咱们作了巨大的调整,“foreach”的遍历中的定义的临时循环变量会被逻辑上限制在循环内,“foreach”的每次循环都会是循环变量的一个拷贝,这样闭包就看起来关闭了(没有了)。但“for”循环没有作修改。
匿名函数和Lambda表达式给咱们的编程带来了许多快捷简单的实现,如(List.Max((a)=>a.Level)等写法)。可是咱们要清醒的意识到这两个糖果后面仍是有个”坑“(闭包)。这再次告诉咱们技术工做人,要”知其然,也要知其因此然“。
Closing over the loop variable considered harmful
Closing over the loop variable, part two
For Loop result in Overflow with Task.Run or Task.Start
Is there a reason for C#'s reuse of the variable in a foreach?
《代码的将来》读书笔记:也谈闭包(介绍较全面,但须要更新C#5的修改,指望博主修改,)