最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

第 74 講:C# 2 之匿名函數(shù)(二):閉包與變量捕獲

2021-12-23 09:54 作者:SunnieShine  | 我要投稿

從 C# 2 匿名函數(shù)機(jī)制發(fā)明出來(lái)之后,C# 引入了一個(gè)全新的概念。它的難度也比較高,因此很多初學(xué)者因?yàn)檫@個(gè)東西的難度而導(dǎo)致無(wú)法繼續(xù)學(xué)習(xí)下去。只要我們能夠一點(diǎn)一點(diǎn)認(rèn)識(shí)它,它其實(shí)并不是我們想象得那么難。

Part 1 還能用和匿名函數(shù)無(wú)關(guān)的變量?!

下面我們來(lái)考慮一個(gè)例子。下面我們要要求輸入一個(gè)數(shù)據(jù)進(jìn)去(用 Console.ReadLine 方法輸入),然后找出整個(gè)數(shù)組序列里,比這個(gè)輸入的數(shù)字要大的數(shù)有哪些。那么我們來(lái)看一下代碼。

這次我們用下才講過(guò)的匿名函數(shù)語(yǔ)法。我們先來(lái)說(shuō)一些生疏的代碼格式和寫法調(diào)用。

  • int.Parse 方法:它是一個(gè)靜態(tài)方法,是 int 類型里面一個(gè)固有的方法。它的作用就是為了提取出一個(gè)只含數(shù)字字符構(gòu)成的字符串里的這個(gè)數(shù)字信息,比如 "123" 里的 123,可以使用 int.Parse("123") 得到;

  • Console.ReadLine 方法:它是一個(gè)靜態(tài)方法,它表示通過(guò)控制臺(tái)輸入一個(gè)東西進(jìn)去。這個(gè)方法是無(wú)參的,但帶有返回值。返回值就是輸入的數(shù)據(jù)。它的讀取過(guò)程是按你按下回車鍵結(jié)束。換句話說(shuō),這個(gè)方法讀取的是你當(dāng)前這一整行里輸入的所有內(nèi)容。

你仔細(xì)看看寫法就可以發(fā)現(xiàn),我把 Console.ReadLine 得到的結(jié)果給直接帶入到了 int.Parse 方法里使用了。雖然這么寫有點(diǎn)奇怪,但這是合情合理的,是吧。因?yàn)槲易x取出來(lái)的內(nèi)容就是一個(gè)字符串啊,我想要的就是把這個(gè)我手頭輸入進(jìn)去的數(shù)據(jù)給改成可以計(jì)算用的數(shù)據(jù),所以沒(méi)有毛病。

那么再來(lái)看看 FindAll 方法。這個(gè)方法也沒(méi)有什么好說(shuō)的,也只是傳入了一個(gè)參數(shù),是委托類型的罷了。但是要注意的是,整個(gè)方法返回的是一個(gè) List<T> 作為結(jié)果,不過(guò)這里我們?cè)诜祷刂道飳懙膮s是 IEnumerable<T>。這個(gè)只是出于封裝的習(xí)慣問(wèn)題。因?yàn)槟惴祷匾粋€(gè) List<T> 對(duì)象的話,意味著你接收方(就是在 Main 方法里)就可以隨便操作這個(gè)結(jié)果了。而假設(shè) FindAll 是別人給的代碼,我們只是調(diào)用一下的話,這不是就沒(méi)有起到安全防御的效果嗎?你想想,我返回一個(gè)結(jié)果是我經(jīng)過(guò)計(jì)算得到的,結(jié)果你卻在調(diào)用的時(shí)候因?yàn)樗?List<T> 而允許去隨意操作它,顯然是不合理的,也不安全。所以,我直接返回值改成接口類型,是盡量讓用戶在使用這個(gè)方法的時(shí)候,能夠最小化了解到這個(gè)結(jié)果可以使用的功能。

思考一下 IEnumerable<T> 是什么用?之前的文章有對(duì)這個(gè)內(nèi)容有所涉及。它表示僅允許對(duì)象使用 foreach 循環(huán)迭代獲取里面的每一個(gè)變量。這是合理的,因?yàn)槲覀冋弥恍枰?foreach 循環(huán)迭代序列而已。因此,我只要改成 IEnumerable<T> 為返回值類型,用戶就會(huì)自動(dòng)認(rèn)為,這個(gè)返回值是拿來(lái)迭代用的,就不會(huì)去考慮拿來(lái)做別的事情。因?yàn)椋?/span>IEnumerable<T> 接口是很多數(shù)據(jù)類型都實(shí)現(xiàn)過(guò)的接口類型,因此只告訴你我實(shí)現(xiàn)了這個(gè)接口,對(duì)方就無(wú)法反推得到原始類型。我們這里知道里面使用的代碼的邏輯用的是 List<T>,但我們是上帝視角,而用戶就不一定知道了。

好了。解釋完了代碼的基本邏輯后,我們來(lái)看匿名函數(shù)的所在語(yǔ)句。

提取出匿名函數(shù)部分(我們只看這一截)。

有人會(huì)問(wèn):“等會(huì)兒!我這個(gè) v 不是 int.Parse 得到的嗎?這不是我 Main 方法里的變量嗎?你怎么能在一個(gè)匿名函數(shù)的代碼里面使用一個(gè)跟匿名函數(shù)無(wú)關(guān)的變量呢?”

是的,這就是我們今天要探討的內(nèi)容。

Part 2 閉包和變量捕獲

2-1 先看結(jié)論

先說(shuō)結(jié)論。這種語(yǔ)法是允許的,它會(huì)自動(dòng)把 v 拿下來(lái)使用。我們來(lái)看這種情況下的完整編譯器生成的代碼。

可以看到,這一次的代碼,編譯器實(shí)現(xiàn)的完整版和之前講解的那個(gè)帶有一個(gè) Closure 類型具有異曲同工之妙。不過(guò)這一次,Closure 類型里并不是兩個(gè)靜態(tài)字段了,而變成了一個(gè) public 的實(shí)例字段 v。沒(méi)錯(cuò),它就是前面我們從 Main 里拿下來(lái)寫進(jìn)匿名函數(shù)的那個(gè) v。接著,Method 方法還是一樣,照抄原本匿名函數(shù)里的實(shí)現(xiàn)邏輯。

只是,這一次我們發(fā)現(xiàn)了異樣:它的完整版本實(shí)現(xiàn)里,v 是作為實(shí)例字段出現(xiàn)的。而 Method 方法照樣還是實(shí)例方法。在這種場(chǎng)合下,它很好理解:因?yàn)檫@次我們要用到 v,而 v 是此時(shí)此刻我們才會(huì)用到的一個(gè)數(shù)值,所以它沒(méi)有 static;而正是因?yàn)樗鼪](méi)有 static,所以 Method 方法也不能是 static 的,否則靜態(tài)方法是不能使用實(shí)例字段的,于是,就有一個(gè)編譯器錯(cuò)誤了。

所以,這次我們可以知道,為什么要把匿名函數(shù)的完整代碼翻譯為一個(gè) Closure 類型了:因?yàn)樗4嫖覀儚哪涿瘮?shù)外部拿到的這些變量的數(shù)據(jù)信息。因?yàn)樗鼈兪菃为?dú)拿下來(lái)的,所以最合理也最方便的辦法就是給這些拿下來(lái)的數(shù)據(jù)單獨(dú)用一個(gè)類的方式體現(xiàn)和存儲(chǔ)起來(lái),這樣一來(lái),我們就可以更靈活地使用匿名函數(shù)了,比如這個(gè)引例所給出的場(chǎng)景。如果沒(méi)有這個(gè)機(jī)制,我們以前是無(wú)論如何都做不到這一點(diǎn)的。由于語(yǔ)法的特殊性,此時(shí)我們也誕生了很多新的概念。首先,我們把這個(gè) Closure 類型稱為閉包(Closure),意味著這個(gè) v 在底層實(shí)現(xiàn)的時(shí)候被封閉到了一個(gè)具體的類型里;而 v 我們此時(shí)稱為外部變量(Outside Variable),它相對(duì)于匿名函數(shù)的內(nèi)部,確實(shí)是存在于匿名函數(shù)這個(gè)執(zhí)行代碼的大括號(hào)的外部的;而變量被拿進(jìn)匿名函數(shù)內(nèi)部使用的過(guò)程,我們稱為外部變量的捕獲(Capture)。

再來(lái)回到 Main 方法里來(lái)看這個(gè)代碼的執(zhí)行邏輯。首先,我們實(shí)例化了一個(gè) Closure 類型的實(shí)例,這次我們沒(méi)有使用 static readonly 組合修飾符,因?yàn)槔锩娴臄?shù)值是當(dāng)前這個(gè)地方才可以用,而別的任何地方都無(wú)法使用,去掉 static 有一個(gè)好處就是防止“全網(wǎng)唯一”。static 關(guān)鍵字意味著內(nèi)存空間在整個(gè)程序里只有一塊地方存儲(chǔ)它,去掉 static 可以預(yù)防用戶隨時(shí)隨地使用這個(gè)特殊的變量。當(dāng)然,readonly 關(guān)鍵字沒(méi)有也就不多說(shuō)了。因?yàn)樽侄味枷Я恕?/span>

接著,我們把原本 int v = 輸入 的語(yǔ)句改成了 closure.v = 輸入。這就是為什么 vpublic 修飾的原因:因?yàn)檫@個(gè)時(shí)候我們并非在 Closure 類型的內(nèi)部操作 v 字段,因此設(shè)置為 public 才可以擴(kuò)大訪問(wèn)范圍;與此同時(shí),因?yàn)檎麄€(gè)類型是 private 修飾過(guò)的,所以即使字段標(biāo)為 public,在任何其它的外部,它也不知道它的存在,畢竟在任何其它的地方,這個(gè) Closure 類型你都看不見。

接著,我們?cè)?FindAll 方法調(diào)用的地方,傳入了委托類型來(lái)代替掉了匿名函數(shù)的語(yǔ)法。注意這里 new Predicate<int>(closure.Method) 的語(yǔ)法。實(shí)例化語(yǔ)句 new 委托 都不用我多說(shuō)了,而這個(gè) closure.Method 是什么東西?

2-2 方法組的概念

方法組是一種特殊的語(yǔ)法格式。它應(yīng)該早在 C# 的原生語(yǔ)法里就存在了。但是因?yàn)樗挠猛静蝗?C# 2 用得頻繁,因此很少對(duì)于它的名字有所提及。包括這個(gè)教程的內(nèi)容,也尚未在之前的內(nèi)容里說(shuō)過(guò)方法組的內(nèi)容。

方法組(Method Group),說(shuō)白了是一個(gè)方法名寫進(jìn)代碼里,使得它和委托概念綁定起來(lái)的一個(gè)特殊語(yǔ)法。目前來(lái)說(shuō),方法組的語(yǔ)法只用于實(shí)例化委托類型。也就是說(shuō),它就用在 new 委托(方法組) 里。它的寫法有三種:

  • 方法名:用于當(dāng)前類型的方法;

  • 實(shí)例對(duì)象.方法名:用于某個(gè)實(shí)例方法,實(shí)例方法綁定執(zhí)行的對(duì)象是這個(gè)指定的實(shí)例對(duì)象;

  • 類型.方法名:用于某個(gè)靜態(tài)方法,表示這個(gè)方法是來(lái)自某個(gè)指定類型的。

我們之前只用到了第一種方法組的語(yǔ)法,因?yàn)槲覀儧](méi)有考慮過(guò)任何其它的情況。是的,我們之前學(xué)習(xí)到的,將方法名當(dāng)參數(shù)傳入到這里的語(yǔ)法機(jī)制,它實(shí)際上是有一個(gè)專業(yè)術(shù)語(yǔ),而其實(shí)這個(gè)語(yǔ)法是叫方法組的。

為什么要叫方法組呢?還記得重載嗎?方法是具有重載的規(guī)則的。這意味著,我們只寫方法名,并不能完整概括和表達(dá)出一個(gè)精確調(diào)用的方法是哪一個(gè)。舉個(gè)例子:

  • C.M();

  • C.M(int);

  • C.M(ref int);

  • C.M(int[]);

它們?nèi)繕?gòu)成重載,因?yàn)閿?shù)據(jù)的類型全部不一樣,而它們的方法名全部相同。可我只寫 M 或者只寫 C.M,我怎么知道我具體到底說(shuō)的是什么方法呢?這可不就是在表示這 4 個(gè)方法的統(tǒng)稱嗎?所以,我們把這個(gè)記號(hào) M 或者的 C.M 叫做方法組,就是因?yàn)檫@個(gè)原因——名稱只能表示一組方法的統(tǒng)稱;只是說(shuō),這一組方法可以包含多個(gè)方法的重載,也可以只包含一個(gè)方法,僅此而已。

那么,下面我們來(lái)說(shuō)一下,剩下兩種寫法的語(yǔ)義和概念。

2-2-1 實(shí)例方法組

考慮一種情況。我們經(jīng)常使用 ToString 方法來(lái)調(diào)用獲取一個(gè)對(duì)象的字符串寫法。而它肯定是支持基本數(shù)據(jù)類型的,比如 intdouble 這些。也就是說(shuō),我如果有一個(gè) int、double 這些數(shù)據(jù)類型的實(shí)例,自然就可以使用實(shí)例方法來(lái)獲取這個(gè)類型的字符串寫法,比如說(shuō),這樣:

注意第 2 行代碼,我們使用 i.ToString() 的目的就是把一個(gè)整數(shù)表示為字符串格式的 "30",即帶有字符 '3' 和字符 '0' 兩個(gè)字符的字符串信息,然后輸出這個(gè)字符串信息。

為什么寫成 i.ToString() 呢?i 是實(shí)例對(duì)象,我們要通過(guò) i 這個(gè)變量來(lái)獲取它的字符串,因此要先寫實(shí)例對(duì)象 i;而我們要獲取字符串,自然調(diào)用的方法肯定是用 ToString 方法。接著,我們調(diào)用方法得傳參,對(duì)吧。只是恰好,ToString 方法它不需要任何參數(shù),因此一對(duì)小括號(hào)里沒(méi)有寫別的東西,但小括號(hào)不能少,對(duì)吧。這個(gè)我們之前就說(shuō)過(guò)了。

那么回到這里。我們明確知道“實(shí)例、方法名、實(shí)際參數(shù)表列”三樣?xùn)|西缺一不可。在委托類型的實(shí)例化的時(shí)候,我們僅僅是通過(guò)一些基本的例子告訴大家了,這個(gè)委托類型的綁定往往跟靜態(tài)方法有關(guān)。實(shí)際上,委托的實(shí)例化也是可以綁定上一個(gè)實(shí)例方法的。

假設(shè),我有這么一個(gè)轉(zhuǎn)換器的委托類型:

它是一個(gè)泛型委托類型,帶有兩個(gè)泛型參數(shù),一個(gè) TIn,一個(gè) TOut。TIn 指的是傳入的數(shù)據(jù)類型,而 TOut 指的是返回帶出來(lái)的數(shù)據(jù)類型。這個(gè) Converter<,> 泛型委托假設(shè)用于表示一個(gè)轉(zhuǎn)換過(guò)程,表示我們將一系列數(shù)組里的每一個(gè)元素從某個(gè)類型的元素轉(zhuǎn)為另外某個(gè)類型的元素,然后組合起來(lái),返回出去的過(guò)程。

接著,我們假設(shè)有一個(gè)單獨(dú)的 Multiplier 類型,用于放大數(shù)據(jù)。

里面的 Convert 方法已經(jīng)說(shuō)得很清楚了,就是用來(lái)放大倍數(shù)用的,而仔細(xì)看看這個(gè)類型,它的成員均為實(shí)例成員,因?yàn)槲覀冞@里做的是一個(gè)乘法器,專門用來(lái)放大倍數(shù)用的一個(gè)轉(zhuǎn)換器,是一個(gè)實(shí)體對(duì)象。所以定義為實(shí)例的沒(méi)有毛病吧?

接著,我們完成 ConvertAll<,> 方法,用來(lái)轉(zhuǎn)換數(shù)組的每一個(gè)元素。按照轉(zhuǎn)換器進(jìn)行轉(zhuǎn)換。

而我在 Main 方法里調(diào)用的時(shí)候,我這么寫代碼:

請(qǐng)注意第 4 行代碼,我們實(shí)例化了 Multiplier 類型的實(shí)例;而我們?cè)?ConvertAll 方法的第二個(gè)參數(shù)里如愿使用上了這個(gè)實(shí)例。我們用的語(yǔ)法是 multiplier.Convert。它表示,以 multiplier 作為實(shí)例,去調(diào)用 Convert 方法,得到結(jié)果。這個(gè) multiplier 就是乘法器的實(shí)例,調(diào)用里面的 Convert 方法就可以完成放大數(shù)值的功能了,而它,確實(shí)也是返回了 int 的數(shù)據(jù);而與此同時(shí),它也是需要傳入一個(gè) int 的參數(shù)的。

請(qǐng)?zhí)貏e注意方法組的用法。這里我們寫的是 multiplier.Convert 而不是 multiplier.Convert(factor),也不是別的什么東西。你始終要記住,我這里是在實(shí)例化委托,而你假如傳入的是 multiplier.Convert(factor),就說(shuō)明你把一個(gè)這個(gè)運(yùn)算的結(jié)果給得到了,然后把這個(gè)結(jié)果當(dāng)成委托實(shí)例的參數(shù)。想想看,這個(gè)執(zhí)行結(jié)果是什么?int。int 能當(dāng)委托實(shí)例化的參數(shù)嗎?顯然不能。

當(dāng)然,這個(gè)具體有些臃腫,因?yàn)樗筒蝗缡褂媚涿瘮?shù)這么寫來(lái)得方便:

這樣雖然簡(jiǎn)單一些,但是捕獲了變量,也不是特別好用。而回過(guò)頭來(lái)看這里,其實(shí)可以發(fā)現(xiàn),其實(shí)我們這個(gè)所謂的 Multiplier,它就是一個(gè)閉包。它把我們給捕獲的 factor 數(shù)值在實(shí)例化 Multiplier 類型的時(shí)候傳到這個(gè)類型的實(shí)例字段里去了。這個(gè)實(shí)現(xiàn)機(jī)制和前面編譯器生成代碼的唯一區(qū)別,只有 new Multipliernew Closure 的實(shí)例化期間,參數(shù)傳參的順序不一樣而已。

  • 編譯器生成代碼:先 new Closure(),這里是無(wú)參構(gòu)造器,然后才是給里面的捕獲字段賦值;

  • 自己寫的這個(gè)閉包:直接 new Multiplier(參數(shù)),就這里就給傳進(jìn)去了。

我們把這里 multiplier.Convert 叫做實(shí)例方法組(Instance Method Group),意味著是綁定一個(gè)實(shí)例對(duì)象的行為。

2-2-2 靜態(tài)方法組

考慮一下,如果這個(gè)方法不在當(dāng)前類里面,我們會(huì)怎么處理呢?我們照樣還是使用前文說(shuō)的 Multiplier 類型來(lái)舉例。假設(shè)我們這次把 Multiplier 看成工具類型,里面的放大函數(shù)就不再使用捕獲變量機(jī)制,而是直接按平方處理這個(gè)數(shù)字:

然后,我們?cè)俅胃膶懘a:

這個(gè) Multiplier.Squared,就是我們所說(shuō)的靜態(tài)方法組(Static Method Group),因?yàn)檗D(zhuǎn)換方法并非存儲(chǔ)在和 Main 方法相同的類型里,因此我們這里需要指定類型名,然后才是方法名。這里正好方法 Squared 是靜態(tài)方法,因此我們僅需指定類型名稱在前面即可,而不是去 new Multiplier() 了,因?yàn)檫@里不是實(shí)例去調(diào)用的這個(gè) Squared 方法了。

那么通過(guò)這里的內(nèi)容介紹,我們可以知道,在前文里“閉包”的完整代碼里,closure.Method 的真實(shí)意思了:它表示一個(gè)實(shí)例方法組,指的是以 closure 實(shí)例去調(diào)用 Method 這個(gè)實(shí)例方法的過(guò)程。

2-3 迭代變量的捕獲

在前文里我們介紹了一個(gè)閉包的完整實(shí)現(xiàn)。可以從代碼里看出,一個(gè)捕獲了變量的匿名函數(shù)就等于是被翻譯成了一個(gè)帶有捕獲變量的實(shí)例字段的一個(gè)類型。

那么,這種捕獲機(jī)制會(huì)不會(huì)存在一些隱藏的問(wèn)題呢?我們考慮下面這個(gè)代碼。

我們來(lái)試著看這段代碼。這段代碼其實(shí)相當(dāng)簡(jiǎn)單,就是一個(gè)循環(huán),創(chuàng)建了 6 個(gè)委托類型的實(shí)例。這 6 個(gè)委托類型的實(shí)例全部都是 Action 類型的,這意味著它們不帶任何參數(shù),并不返回任何數(shù)值。

另外,請(qǐng)看循環(huán)里面的代碼。我們使用 delegate { ... } 這個(gè)匿名函數(shù)來(lái)完成對(duì) list[i] 的實(shí)例化和初始化。在這個(gè)匿名函數(shù)里,我們用到了匿名函數(shù)本身無(wú)關(guān)的外部變量 i,它是這個(gè)循環(huán)里使用到的循環(huán)變量。

按照我們的思維來(lái)說(shuō),這個(gè)循環(huán)變量是在每一次執(zhí)行 list[i] = delegate { ... } 的時(shí)候才會(huì)被調(diào)用和使用到,因此按理說(shuō),i 是不一樣的,即在第 7 行開始,調(diào)用 a.Invoke() 應(yīng)該輸出顯示的是 0、1、2、3、4、5 這幾個(gè)數(shù)??墒牵?qǐng)你打開 Visual Studio 就可以發(fā)現(xiàn),結(jié)果并不是這樣的。實(shí)際上的結(jié)果比你想象得還要離譜。

哈?這是怎么一回事呢?這我們就得深掰一下捕獲機(jī)制的基本原理了。

我們知道,什么時(shí)候匿名函數(shù)都是被翻譯成了一個(gè)閉包(即一個(gè)類型,存儲(chǔ)了這些實(shí)際的捕獲變量的數(shù)據(jù))??蓡?wèn)題在于,循環(huán)變量是我們不斷變動(dòng)的數(shù)據(jù)。而完整的代碼,按照我們?cè)瓉?lái)的思路,它被翻譯出來(lái)的代碼應(yīng)該是這樣的:

請(qǐng)仔細(xì)看代碼。closure.i 是我們閉包里為了存儲(chǔ)捕獲變量 i 而單獨(dú)開設(shè)的字段;而在循環(huán)體里,我們并沒(méi)有任何地方用到這個(gè) i,反而是這個(gè) for 循環(huán)的末尾增量處,closure.i++ 執(zhí)行了語(yǔ)句,在不斷改變 i 的數(shù)值。

可問(wèn)題就出在這里。我們不斷在更新 i 這個(gè)捕獲變量的數(shù)值,但循環(huán)內(nèi)我們實(shí)例化 Action 的時(shí)候,并沒(méi)有涉及任何關(guān)于這個(gè) i 的使用;相反,我們可以看到,這個(gè) Closure 類型里的實(shí)例方法 Method(對(duì)應(yīng)了匿名函數(shù)里的代碼),在用到這個(gè)捕獲變量 i。而此時(shí),這個(gè) i 并不是 for 循環(huán)里面的那個(gè) i 了,而已經(jīng)是這個(gè)閉包里的這個(gè)字段 i 了。

我們?cè)诘?3 行不斷增大 i 字段的數(shù)值,而整個(gè)委托數(shù)組都只在第 7 行才開始調(diào)用它們。問(wèn)題就在于,第 7 行才開始使用就意味著我已經(jīng)把第 3 和第 4 行的委托數(shù)組對(duì)應(yīng)的位置都實(shí)例化過(guò)一次了,i 此時(shí)的數(shù)值應(yīng)該是 6(i 是從 0 開始的,它反復(fù)在 for 循環(huán)里增大增大增大,因此最終肯定是第一次不滿足 for 循環(huán)的條件的時(shí)候跳出循環(huán),因此 i 最終肯定是 6)。而你又在對(duì)這個(gè) 6 在調(diào)用和輸出顯示結(jié)果,你說(shuō)說(shuō),是不是應(yīng)該是 6 個(gè) 6?

所以,這一點(diǎn)相當(dāng)隱晦,但也確實(shí)在情理之中。那么,我們來(lái)針對(duì)于這個(gè)特殊捕獲情況做一個(gè)總結(jié)吧。

當(dāng)捕獲變量涉及循環(huán)變量(forforeach 循環(huán))的時(shí)候,最終在匿名函數(shù)里使用的這個(gè)循環(huán)變量并非當(dāng)前循環(huán)體里此時(shí)的數(shù)值,而是循環(huán)已經(jīng)完成后的結(jié)果數(shù)值。

啊,是的,foreach 循環(huán)的結(jié)果也是如出一轍。

是的,這樣的話,結(jié)果應(yīng)該是顯示三個(gè) "Aki"。不過(guò),這個(gè) foreach 捕獲迭代變量的特殊現(xiàn)象在 C# 5 所改變,這一點(diǎn)我們到時(shí)候再說(shuō),你先照著記就行了。

Part 3 參數(shù)名影射

C# 誕生了匿名函數(shù)和閉包機(jī)制之后,就產(chǎn)生了一些新鮮的思維邏輯和語(yǔ)法。比如……參數(shù)名影射(Parameter Name Shadowing)。

如代碼所示,這里用到了 4 個(gè) f。它們是同一個(gè)東西嗎?實(shí)際上,并不是。這種現(xiàn)象稱為參數(shù)名影射,就是說(shuō)匿名函數(shù)的參數(shù)可以用變量定義的名稱,因?yàn)樗鼈儾⒉皇峭粋€(gè) f。

考慮到,假設(shè)這個(gè)定義語(yǔ)句被放在 Main 方法里的話,那么第 1 行的這個(gè) f 就是 Main 方法里的一個(gè)普通變量。而這個(gè)變量用了一個(gè)匿名函數(shù)來(lái)賦值。而這個(gè)匿名函數(shù)的參數(shù)名也是 f。注意,這個(gè) f 僅表示匿名函數(shù)的參數(shù)名稱,跟第 1 行的 f 沒(méi)有任何關(guān)系。另外,從第 2 行開始定義匿名函數(shù)這里,到第 8 行結(jié)束,期間的代碼里,這個(gè) f 是可以使用的范圍。

接著,在第 4 行又是一個(gè)匿名函數(shù),它也用到了一個(gè) f,這跟第 1 行和第 2 行的 f 也都沒(méi)有關(guān)系,它也只是這個(gè)匿名函數(shù) delegate (int f) { return f; } 的參數(shù)名,因此,第 6 行和第 4 行的 f 才是同一個(gè) f


至此,我們基本上對(duì)閉包有了一個(gè)全面和深入的了解。這下我們算是終于知道,為什么是一個(gè)類型來(lái)充當(dāng)?shù)哪涿瘮?shù)了。雖然這一講的內(nèi)容非常難,但是下一節(jié)的內(nèi)容……更難。請(qǐng)做好心理準(zhǔn)備吧。


第 74 講:C# 2 之匿名函數(shù)(二):閉包與變量捕獲的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
和政县| 阿鲁科尔沁旗| 忻州市| 布拖县| 雷州市| 岑巩县| 英吉沙县| 广平县| 东台市| 巴东县| 武邑县| 利川市| 巴青县| 崇阳县| 额尔古纳市| 得荣县| 武陟县| 沧州市| 墨竹工卡县| 资中县| 苍山县| 兴隆县| 盈江县| 遂宁市| 阜新市| 如皋市| 堆龙德庆县| 墨江| 公安县| 泾源县| 当涂县| 贡山| 远安县| 鄯善县| 卢氏县| 西和县| 林甸县| 和平县| 开封市| 保靖县| 会东县|