閉包是什麼?之前看面試題的時候才發現這個名詞。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的修改,指望博主修改,)