C 委託 Lambda表示式 閉包和記憶體洩漏

2022-04-12 10:27:07 字數 4014 閱讀 1651

首先看看委託的常見的使用情景:定義乙個委託、使用剛定義的委託宣告乙個委託變數、根據需要將方法和該變數繫結,最後在合適的地方使用它。**形式如下:

//

定義委託

public

delegate

void

somedelegate();

class

someclass

public

static

void

staticfunction()

}public

class

someuserclass

}

先不談委託的其他用途,通過上面的例子,可以將委託簡單理解為乙個「方法型別」。可將委託宣告的變數和與委託簽名相符的方法繫結,之後就可以像使用方法一樣使用這個變數。

委託是安全封裝方法的型別,類似於 c 和 c++ 中的函式指標。 與 c 函式指標不同的是,委託是物件導向的、型別安全的和可靠的。 委託的型別由委託的名稱確定。——來自msdn

上面的做法是將委託變數del分別與乙個例項方法和乙個靜態方法繫結。這兩種方式都被稱作使用命名方法。

在 c# 1.0 中,通過使用在**中其他位置定義的方法顯式初始化委託來建立委託的例項。 c# 2.0 引入了匿名方法的概念,作為一種編寫可在委託呼叫中執行的未命名內聯語句塊的方式。 c# 3.0 引入了 lambda 表示式,這種表示式與匿名方法的概念類似,但更具表現力並且更簡練。 這兩個功能統稱為匿名函式。 通常,面向 .net framework 3.5 及更高版本的應用程式應使用 lambda 表示式。——來自msdn

我個人是更加偏好於使用lambda表示式,至於匿名方法,用法幾乎與lambda表示式一樣。下文的示例**中我都將用更加簡潔的lambda表示式來書寫。lambda表示式可以參考msdn——lambda表示式

func實際上是.net封裝好的乙個委託,它不接受引數、返回乙個tresult型別的值。

比如我們可以通過如下**來宣告乙個func的變數、並為其繫結乙個方法、然後使用它:

public

class

anotherclass

private

void

someuserfunction()

}

對於上面的**,如果改用lambda表示式,就會簡潔很多,如下:

public

class

anotherclass;

//通過變數呼叫方法

int result =funcint();

//do something

}}

使用lambda表示式省掉了書寫命名方法的過程,**看起來更加清新。然而,稍不注意,lambda表示式就會「毀滅」你的**。

在lambda表示式「毀滅」你的**前,先看看下面的**會輸出什麼:

listint>> funcs = new listint>>();

for (int i = 0; i < 3; i++));}

foreach(var item in

funcs)

對於不理解閉包的人,第一反應自然是輸出0、1、2。但事實上,它輸出的是3、3、3。造成這種「出人意料」的結果的原因,就是閉包。

關於閉包,這裡不作過多、過複雜的介紹,想要深入了解,可以查閱相關資料。

簡單地講,閉包是乙個**塊(在c#中,指的是匿名方法或者lambda表示式,也就是匿名函式),並且這個**塊使用到了**塊以外的變數,於是這個**塊和用到的**塊以外的變數(上下文)被「封閉地包在一起」。當使用此**塊時,該**塊裡使用的外部變數的值,是使用該**塊時的值,並不一定是建立該**塊時的值

一句話概括,閉包是乙個包含了上下文環境的匿名函式。

有點拗口,不過暫且先根據這個解釋,我們回去看看上面的**。

**中的lambda表示式(**塊)() => ,使用了for迴圈中的迴圈變數i。

在for迴圈中,我們通過lambda表示式(**塊)建立了三個匿名函式、並新增進委託列表中;當for迴圈結束後,我們逐個呼叫與委託列表繫結的三個匿名函式。

在呼叫這三個匿名函式時,雖然for迴圈已經結束,其控制變數i也「看起來不存在了」,但事實是,變數i已經被加入到上面每乙個匿名函式各自的上下文中,也就是說,上面的三個匿名函式,都「閉包」著變數i。

此時i的值已經等於3,於是這三個匿名函式都將返回3並交給console去輸出。

為了看清楚後台究竟發生了什麼,用visual studio自帶的il disassembler開啟編譯出的exe檔案,檢視結果。

對於閉包,編譯的結果是:編譯器為閉包生成了乙個類,i作為乙個公共的字段存在於其中。

也就是說,雖然for迴圈已經結束,但是i仍然以一種「看不見」的方式活躍在記憶體中。所以當我們呼叫這三個匿名函式時,使用的都將是同乙個i(指的是變數,而不是它具體的值)。

接下來修改**如下:

listint>> funcs = new listint>>();

for (int i = 0; i < 3; i++));}

foreach(var item in

funcs)

再次執行,輸出結果為0、1、2。分析下原因。

在每一次迴圈時,我們都建立了乙個新的變數j。為了區分每一次迴圈中的j,第一次迴圈時,我稱它為j0,此時它從i中獲得的值為0,並且本次迴圈中,建立了乙個匿名函式並使用了j0,形成了乙個閉包。在第二次迴圈時,將建立另乙個變數j1,此時它從i中獲得的值為1,此迴圈中的匿名函式將使用變數j1,形成另乙個閉包;第三次迴圈類似。

一下子豁然開朗了。在這次的**中,三個匿名函式使用的j並不是同乙個變數,所以會有後面的結果。

還是先看一段**:

list values = new list() ;

list

int>> funcs = new listint>>();

foreach (var item in

values));}

foreach(var item in

funcs)

這段**的輸出是0、1、2。看起來似乎與前面所講的有矛盾。

在c# 5.0之前的版本,在foreach的迴圈中,將會共用乙個item,這段**的輸出就是2、2、2;c# 5.0之後,foreach的實現方式作了修改,在每一次迴圈時,都會產生乙個新的item用來存放列舉器當前值,所以此時的情形類似於上面for迴圈的第二種情形。

再看一段**:

class

program

private

static

void

showmemory()

public

class

memoryholder

~memoryholder()

}public

class

someclass

;console.writeline(

"function exited");}}

}

看看執行結果:

可以看出,原本在somefunction呼叫結束時就應該被釋放的memoryholder物件,並沒有被釋放,而是在使用它的閉包被釋放時,才真正被釋放掉。也就是說,閉包會延長它使用的外部變數的生命週期,直到閉包本身被釋放。

那麼閉包會不會造成記憶體洩漏?

我認為只有不嚴謹的**才會造成記憶體洩漏。正如上述**中的someclass.func或者someclass物件、在不需要它(們)的時候沒有被正確釋放它(們),就會造成了本該被銷毀的holder物件不會被正確地被銷毀、自然也就造成了記憶體洩漏。但是不應該讓閉包背這個鍋。

1、匿名函式是個語法糖,很方便,但是也容易帶來問題。

2、如果一定要使用閉包,那麼切記做好記憶體的**。

3、養成良好的**習慣。

C 中委託和Lambda表示式

什麼是委託?簡單來說委託是乙個型別,這個型別可以賦值乙個方法的引用。delegate return type delegate name parameter type parameter name,1.delegate name delegate instance new delegate name...

關於C 委託和Lambda表示式

關於c 委託和lambda表示式 1 c 委託和lambda表示式結合定義方法非常方便 在定一次性方法有很好的應用 沒有返回值的委託和lambda表示式結合 普通委託 getproductnoreturn getproductnoreturn a 沒有引數的委託和lambda表示式結合 普通委託 g...

委託 lambda表示式

呼叫委託的的時候,其包含的每乙個方法都會被執行。委託要求,定義的簽名和返回型別必須一致。建立委託需要注意的地方 因為大部分的委託需要重用,而事件不用,所以前者寫在類外邊,後者寫在類裡邊。一般委託寫在類的外邊,並且用public宣告。委託只有乙個規矩,就是輸入輸出一樣就行,那他屬於乙個委託,你可以把它...