持續更新中…
引言
看過一些文章,總結下來就是,閉包是一個帶共享數據的函數。內層的函數可以引用包含在它外層的函數的變量,即使外層函數的執行已經終止。但該變量提供的值並非變量創建時的值,而是在父函數範圍內的最終值。嵌套定義函數,使用了外部定義域(非全局定義域)的變量。是否有返回值並不影響判斷。
示例
這三個函數幾乎一樣,唯一不同的就是action函數裏變量。
函數action => Console.WriteLine(x)
關聯的變量是x
。這個匿名函數以該形式保存。直至被調用時,纔會去取對應的i
。
private static void F1()
{
IList<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
}
ouput:5 5 5 5 5
這個例子比較容易理解。循環結束後,i=5
。所以去遍歷actions
時,所有的action => Console.WriteLine(x)
使用的是變量i
,因此結果是 5 5 5 5 5。
private static void F2()
{
IList<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
int j = i;
actions.Add(() => Console.WriteLine(j));
}
foreach (var action in actions)
{
action();
}
}
ouput:0 1 2 3 4
這裏j
是循環裏的局部變量,並不受i++
的影響。action => Console.WriteLine(x)
使用的是變量j
,在每次循環後就已經固定下來。因此結果是 0 1 2 3 4。
private static void F3()
{
int j;
IList<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
j = i;
actions.Add(() => Console.WriteLine(j));
}
foreach (var action in actions)
{
action();
}
}
ouput:4 4 4 4 4
這個的思路與F1類似。只是因爲循環結束後,i=5
,j=4
。因此結果是 4 4 4 4 4。但與F2做比較,就能發現他們的重大區別,很有參考意義。
應用
上面的例子只是一種很標準的閉包格式。其實很多時候我們在使用lambda表達式時候,已經屬於閉包範圍了。如有一個list數據,我們希望通過多線程,每個線程處理其中的一個對象。於是有如下正確與錯誤的示例。
List<UserModel> userList = new List<UserModel>
{
new UserModel{ UserName="A", UserAge = 1},
new UserModel{ UserName="B", UserAge = 2},
new UserModel{ UserName="C", UserAge = 3}
};
// 錯誤
for (int i = 0; i < 3; i++)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
Thread.Sleep(100);
UserModel u = userList[i];// i永遠都是userList.Count
Console.WriteLine("case1:\t" + u.UserName);
// 等價於
// Console.WriteLine("case1:\t" + userList[i].UserName);
});
}
// 正確
for (int i = 0; i < 3; i++)
{
UserModel u = userList[i];
ThreadPool.QueueUserWorkItem((obj) =>
{
Thread.Sleep(100);
Console.WriteLine("case2:\t" + u.UserName);
});
}