[C#學習筆記] - C#中閉包的一些理解

持續更新中…

引言

看過一些文章,總結下來就是,閉包是一個帶共享數據的函數。內層的函數(子函數)可以引用包含在它外層函數(父函數)的變量,即使外層函數的執行已經終止。但該變量提供的值並非變量創建時的值,而是在父函數範圍內的最終值。嵌套定義函數,使用了外部定義域(父函數定義域)(非全局定義域)的變量。是否有返回值並不影響判斷。

javascript的閉包理解:函數可以使用函數之外定義的變量。

示例

這三個函數幾乎一樣,唯一不同的就是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=5j=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);
    });
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章