程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 理解C#中的閉包

理解C#中的閉包

編輯:C#入門知識

閉包的概念 內層的函數可以引用包含在它外層的函數的變量,即使外層函數的執行已經終止。但該變量提供的值並非變量創建時的值,而是在父函數范圍內的最終值。   閉包的優點 使用閉包,我們可以輕松的訪問外層函數定義的變量,這在匿名方法中普遍使用。比如有如下場景,在winform應用程序中,我們希望做這麼一個效果,當用戶關閉窗體時,給用戶一個提示框。我們將添加如下代碼:   復制代碼 private void Form1_Load(object sender, EventArgs e) {        string tipWords = "您將關閉當前對話框";        this.FormClosing += delegate        {             MessageBox.Show(tipWords);        }; } 復制代碼 若不使用匿名函數,我們就需要使用其他方式來將tipWords變量的值傳遞給FormClosing注冊的處理函數,這就增加了不必要的工作量。   閉包陷阱 應用閉包,我們要注意一個陷阱。比如有一個用戶信息的數組,我們需要遍歷每一個用戶,對各個用戶做處理後輸出用戶名。   復制代碼 public class UserModel {         public string UserName         {             get;             set;         }           public int UserAge         {             get;             set;         } } 復制代碼 復制代碼 List<UserModel> userList = new List<UserModel>             {                 new UserModel{ UserName="jiejiep", UserAge = 26},                 new UserModel{ UserName="xiaoyi", UserAge = 25},                 new UserModel{ UserName="zhangzetian", UserAge=24}             };               foreach (var u in userList)             {                 ThreadPool.QueueUserWorkItem((obj) =>                 {                     //TODO                     //Do some process...                     //...                     Thread.Sleep(1000);             MessageBox.Show(u.UserName);                 });             } 復制代碼 我們預期的輸出是, jiejiep, xiaoyi, zhangzetian   但是實際我們運行後發現,輸出了3次 zhangzetian.   為什麼沒有達到我們預期的效果呢?讓我們再來看一下閉包的概念。內層函數引用的外層函數的變量的最終值。就是說,當線程中執行方法時,方法中的u參數的值,始終是foreach遍歷的最後一個元素。原來如此,那我們該如何避免閉包陷阱呢?C#中普遍的做法是,將匿名函數引用的變量用一個臨時變量保存下來,然後在匿名函數中使用臨時變量。   復制代碼 foreach (var u in userList)             {                 UserModel tempUser = u;                 ThreadPool.QueueUserWorkItem((obj) =>                 {                     //TODO                     //Do some process...                     //...                     Thread.Sleep(1000);              MessageBox.Show(tempUser.UserName);                 });             } 復制代碼 我們再運行來看,輸出依次為 jiejiep,xiaoyi, zhangzetian   NET編譯器與閉包 提出了問題,給出了解決方案,我們總算知道該怎麼正確使用閉包了。但是dotNET是如何實現閉包的呢?執著的程序猿們,不會滿足於這種表象的解決方案,讓我們來看看dotNET是如何實現閉包的。我們可以微軟提供的isdasm.exe來查看編譯後的代碼。我們先來看看有問題的代碼。將IL代碼翻譯後,可以得到如下的偽代碼。   復制代碼     public class TempClass     {         public UserModel um         {             get;             set;         }           public void ShowMessage(object obj)         {             Thread.Sleep(1000);             MessageBox.Show(um.UserName);         }     } 復制代碼 復制代碼         TempClass tempCls = new TempClass();         foreach (var u in userList)         {              tempCls.um = u;                ThreadPool.QueueUserWorkItem((obj) =>              {                 tempCls.ShowMessage(obj);              });         } 復制代碼 原來,編譯器為我們生成了一個臨時類,該類包含一個 UserModel成員um, 一個實例方法 ShowMessage(object) 。再看看遍歷部分的代碼,我們頓時就豁然開朗了,原來一直都只有一個 TempClass實例,遍歷時始終改變的是tempCls對象的um字段的值。所以最後輸出的,始終是最後一個遍歷得到的元素的 UserName 。   我們再來看看使用臨時變量後的代碼,編譯器是如何處理的呢。   復制代碼 foreach (var u in userList) {     TempClass tempCls = new TempClass();     tempCls.um = u;       ThreadPool.QueueUserWorkItem((obj) =>     {          tempCls.ShowMessage(obj);     }); } 復制代碼 我們看到,使用臨時變量這種解決方案時,編譯器相當於是每次遍歷時都實例化了一個 TempClass對象。所以內層函數引用的tempCls的um成員始終是遍歷對應的元素。故能有效的解決閉包帶來的陷阱。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved