天天看點

C#溫故而知新系列 -- 閉包

閉包的由來

   要說閉包的由來就不得不先說下函數式程式設計了。近幾年函數式程式設計也是比較火熱,我們先來看看函數式程式設計的一些基本的特性這個有助于我們了解閉包的由來。

   函數式程式設計

     函數式程式設計是一種程式設計模型,他将計算機運算看做是數學中函數的計算,并且避免了狀态以及變量的概念。這裡很明顯的指出了函數式程式設計中最重要的就是函數而且是數學中的函數,比如f(x),數學中的函數最大的特點就是隻要是同樣的參數x那麼我的結果必定是相等的,也就是說我們函數的傳回值隻是依賴于參數而不依賴于其他狀态(比如js中的全局變量就是一個幹擾因素);後一句中說避免變量的概念,這句話如果從函數式程式設計來說不太恰當,因為這句話中的函數意思還是我們在程式設計語言中所使用的變量也就是一個存儲單元,而在函數式程式設計中變量卻是數學中變量的定義是一個值得名稱。比如,我們最基本的指派等式 x = x+1,讓我們程式員看這是一個簡單的指派代碼,而讓學數學的人來說這個等式是根本不成立的。 是以我們在函數式程式設計中是不允許多次指派的。而這一句話也是講述了函數式程式設計好處的最主要的原因:

   第一點、函數的結果隻依賴于參數而不依賴其他狀态,這樣寫的代碼很容易進行推理不容易發生錯誤,極大的友善的單元測試和調試。

   第二點、因為不可變性和無狀态,那麼我們在處理多個線程之間就不用擔心資源的争奪,不需要用鎖來儲存狀态。

   高階函數

      函數式程式設計中函數是一等公民,和我們的口号 "萬物皆對象"有點相似,在函數式程式設計中,我們努力用函數來表達所有的事情,當然我們也需要函數可以傳過來傳過去這就是高階函數,也就是把函數作為參數或者傳回值,繼而實作複用,這樣即是可以把複用的粒度降到函數。C#語言中也有類似的東西--委托,當然C#中的函數跟函數式程式設計中的就不一樣了,但是有吸收一些函數式程式設計語言中的特性,比如C#中lamda,Linq。

     關于函數式程式設計部落格園有很多很好的文章介紹我就不詳說了,接下來就是引出我們今天的主題--閉包。

    因為函數式程式設計的基礎就是Lambda演算,是以這一節演算我們用Lambda演算來帶出我們的主題,關于這個演算我也懂得不是太多,想要入門的同學可以看看這個 

點這裡

首先定義一個簡單的演算

    λx.λy.x+y

如果x為1 y為2 演算過程則為

  ((λx.λy.x+y)1)2=(λy.1+y)2=(1+2)=3

接下來我們用到高階函數 

   λy . (λx . x + y)

演算過程:

  ((λy . (λx . x + y))1)2=((λx . x + 1))2 = (2+1)=3

可以看到這個演算中外層函數使用的是内層函數,也就是說使用是一個函數作為了計算結果。OK ,我們把内層函數單獨拿出來,(λx . x + y),可以很明顯的看到如果脫離的上下文呢,我們的y是沒有值的,也就是y是沒有綁定的,也可以說y對于我們這個函數是自由的! 如果在函數式程式設計中,在外層函數執行完畢之後我們的y變量就應該被銷毀,那麼如果我們在内層函數中如果還需要用到y的話怎麼辦呢? 對于這個問題,設計者則做了其他的處理:如果一個函數傳回另一個函數,而被傳回函數又需要外層函數的變量時,不會立即釋放這個變量,而是允許被傳回的函數引用這些變量。支援這種機制的語言稱為支援閉包機制,而這個内部函數連同其自由變量就形成了一個閉包(這句話是摘自其他部落格,自己難得整理文字。。。)。這就是我們閉包的由來,而我們其他的語言如果有用到函數式程式設計的思想,并且允許函數來進行傳遞就會遇到類似的問題,是以各個語言就需要用其自己的方式來實作閉包!

C#中閉包的實作

     從上一節我們也就是能總結出閉包其實就是要執行并且包含自由變量的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環境的一個結合。 

然後進入我們的C#程式設計時刻了,我們就用簡單的例子來實作,并且檢視編譯器生成的代碼 看看C#中是怎麼實作閉包,畢竟我們也是有委托的 。。。

     首先寫一個簡單得不能在簡單的代碼

1 using System;
 2 
 3 namespace closure
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Console.WriteLine(test(1)(2));
10             Console.ReadKey();
12         }
13 
14         public static Func<int,int> test(int x)
15         {
16             //作用域1
17             return (y) =>  
18             {
19                //作用域2
20                return x + y;
21             };
22         }
23     }
24 }      

   可以看到我們test的方法中傳入變量x的作用域是在1 在執行匿名函數的時候應該是已經釋放在作用域2就不應該存在了,而我們卻能準确的得到計算結果    

C#溫故而知新系列 -- 閉包

  說明我們的變量x确實在作用域2中還存在,接下來我們看看編譯器幫我們做了什麼事情,

C#溫故而知新系列 -- 閉包

  可以看到我們的test方法中多了一個對象 <>c__DisplayClass1_0  class_;這個東西的具體定義是啥?

C#溫故而知新系列 -- 閉包

   這個很明顯了,其實閉包隻是編譯器幫我們把自由變量封裝到了一個對象中供我們作用域外使用,那我們如果去掉作用域2中使用x變量呢?

C#溫故而知新系列 -- 閉包

  編譯器原來為了自由變量維護的對象沒了 。。。結果在意料之中。

OK,這篇文章就到此結束了,關于閉包是python中啊 js中啊 或者C#中得用處我就不細說了,部落格園中有太多太多的介紹了,希望這邊文章對你有幫助,歡迎拍磚!