天天看點

C#中的委托,匿名方法和Lambda表達式

原文位址為: C#中的委托,匿名方法和Lambda表達式

簡介

  在.NET中,委托,匿名方法和Lambda表達式很容易發生混淆。我想下面的代碼能證明這點。下面哪一個First會被編譯?哪一個會傳回我們需要的結果?即Customer.ID=5.答案是6個First不僅被編譯,并都獲得正确答案,且他們的結果一樣。如果你對此感到困惑,那麼請繼續看這篇文章。

什麼是委托?

  現在你定義一個處理使用者訂單的購物車ShoppingCart類。管理層決定根據數量,價格等給客人折扣。做為其中的一部分,他們已經實作了處理訂單時你要考慮一方面。不用考慮過多,你簡單聲明一個變量來儲存有“吸引力的折扣”(magicDisCount),然後實作邏輯。

  第二天,異想天開的管理層決定根據購買時間調整折扣。這個很簡單,但需要你改動一點代碼。

  接下來一段時間裡,管理層又反複添加更多的折扣邏輯。這時你就會在心理抱怨“受夠了”。那麼我該怎麼做才能把這些無聊的邏輯從我的代碼中剝離出去,讓該處理的人去處理呢?這時你要做的是移交或者委派給相應職能的别人。幸運的是,.NET為此提供了一種叫做“委托”的機制。

委托

  如果你有C/C++程式設計背景,描述委托最好的方法是“函數指針”。對所有人來說,可以認為把委托傳遞給方法與把值或對象傳遞給方法一樣。比如下面三行代碼就表現出一樣的基本原則:你在傳遞資料給Process處理而不是你自己使用。

  DiscountDelegate是什麼?我如何建立?Process方法如何使用?首先如同聲明一個類一樣,聲明一個委托類型。

  這句話的意思是我們有一個叫DiscountDelegate的委托類型,我們可以像使用類,結構體等一樣使用它。它不需要資料參數,但傳回一個整數值。像類一樣,我們必須建立一個它的執行個體它才有意義。記住,建立一個委托執行個體實質上是建立一個方法的引用。建立執行個體時關鍵是要明白DiscountDelegate沒有任何構造器,它有一個隐式的構造函數來構造一個與它相同簽名的方法(沒有傳入參數,傳回一個整數)。那你怎麼給這個構造函數一個方法呢?.NET向你提供了一個向它名字一樣簡單的方法,你所做的隻是忽略圓括号。      
  在深入之前,先回到開始的例子,整理一個代碼。我們會添加一個Calculator類來幫助我們處理折扣邏輯,并給我們的委托提供一些方法。
        

  正如你所見,在Calculator類中,我們為每個邏輯分支建立了一個方法。在Main方法中,我們建立一個Calculator執行個體和一個DiscountDelegate執行個體,并按照我們所期望的把它們整合在一起。

  太棒了,我們不用擔心Process方法中的邏輯了,我們隻需要簡單得回調我們定義的委托。記住!我們不關心委托是如何建立的(或什麼時間),我們就像調用其他方法一樣調用它。如你所見,另一種了解委托的方法是,它延遲執行一個方法。Calculator方法在過去某個時間本選擇,但不會執行,直到我們調用discount()的時候。現在看看我們的解決方案,這裡仍然存在一些醜陋的代碼。在Calculator類中,我們可以用一個不同的方法來傳回替代每個有傳回值得方法嗎?答案是肯定的,讓我們把這些亂糟糟的代碼合并起來。

  這樣子看起來更好點。你會注意到我們用一個靜态的Calculate方法替換了所有原來的方法,在Main方法中也不用費心維護一個指向DiscountDelegate的引用。現在你明白了所有關于委托的東西了嗎?在2004年.NET1.1中可以這麼說,但是很不幸的是,這種架構自那以後更加成熟了。

燈光,鏡頭,開始 或者我們需要Func!

  微軟在.NET 2.0中引入了泛型,并提供了一個泛型委托:Action<T>。老實說,我認為它遠不夠用。後來在.NET 3.5中,它為我們提供了一些我們不想定義的通用委托。他們擴充了Action,并添加了Func,二者唯一差別在于Func型方法有一個傳回值而Action型方法沒有。

  這意味着我們不需要聲明自己的DiscountDelegate,可以用Func<int>替代。為說明這些觀點是如何工作的,我們來假設管理層又一次改變了我們的邏輯,我們需要提供一些特殊的折扣。很簡單,我們将給Calculate方法傳入一個bool型值。

  現在我們的委托簽名變成Func<bool,int>。注意Calculate方法現在包含一個bool型參數,我們用一個bool值調用discount()。

  好像還算不錯,我們又省了一行代碼,這樣算結束了嗎?當然沒有,我們甚至能省掉類型判斷。隻要我們傳遞的方法有嚴格簽名的委托,.NET允許我們完全忽略掉顯式建立Func<bool,int>。

  至此,首先通過忽略自定義委托,我們省略了代碼;然後排出了明确的建立Func委托。我們能繼續壓縮代碼行嗎?到此我們才完成此文的一半,答案顯然是“能”。

 匿名方法      

  匿名方法能夠讓你聲明一個方法體而不需要給它指定一個名字。在接下來的場景裡,它們以一個“普通的”方法存在;但是在你的代碼中沒有任何方法顯式調用它。匿名方法隻能在使用委托的時候建立,事實上,它們通過delegate關鍵字建立。

  正如你所見,我們完全删除了Calculator類的需求。你可以在打括号中添加任何其他方法中的邏輯。如果你在看它如何執行時有困難,那就把delegate(bool x)做為一個方法簽名,而不是一個關鍵字。設想這段代碼在一個類裡,delegate(bool x){return 5;}是一個完整的合法方法聲明(我們确實有一個傳回值),恰好delegate是一個保留字,在這裡,它讓這個方法匿名。

  至此,我确信現在你知道這裡我們甚至能壓縮更多的代碼。順利成章的,我們能忽略顯式聲明Func委托的需要;.NET讓我們使用delegate關鍵字更友善。

  當把.NET方法做為委托參數時或處理時間時,就能看到匿名方法的真正用處。之前,你會為你所關注的所有可能行為建立了一個方法,現在你僅需以内聯的方式建立它們,并可以避免污染你的命名空間。

Lambda 表達式

  MSDN中寫道:“Lambda 表達式”是一個匿名函數,它可以包含表達式和語句,并且可用于建立委托或表達式樹類型。你應當明白“使用者建立委托”部分,但什麼是“表達式”呢?老實說,表達式和表達式樹不在此為讨論範圍内。現在我們唯一需要明白的是,表達式是.NET程式運作時表示資料或對象的代碼(C#代碼)。引用Jon Skeet的話:“表達式樹是一種表達邏輯,這樣其他的代碼可以查詢的方法。當一個lambda表達式轉換成一個表達式樹,編譯器不會發出了lambda表達式的白細胞介素,它會發出白細胞介素這将會建立一個表達式樹表示相同的邏輯。”

  我們需要關注的是Lambda表達式替換匿名方法,和其他的特性。回顧我們最後例子,我們已經在一行代碼裡壓縮了處理整個折扣算法的邏輯。

  你相信我們能讓這個更短嗎?Lambda表達式用'=>'運算符表明什麼參數傳遞給表達式。編譯器進一步處理,允許我們忽略類型并自動替我們推斷這些類型。如果你有2個或更多個參數,你需要用圓括号:(x,y)=>。如果隻有一個,你設定不需要這樣:x=>。

  就是這樣子。x被推斷為bool型,并且有傳回值,因為Process接收一個Func<bool,int>。如果我們想實作像之前那樣的完整代碼塊,我們隻需要加上大括号。

寫在最後

  使用與不使用大括号有一個重要的不同。當你用時,你建立一個“語句Lambda”,反之,它是"表達Lambda"。語句Lambda能執行多條語句(是以需要大括号),但不能建立表達樹。你可能隻在使用IQueryable接口是遇到這個問題。下面的例子說明這個問題。

  倒數第二行在編譯時失敗。這是因為IQueryable.First期望得到一個表達式作為參數,然而List<T>.First期望得到一個委托。你可以按照最後一行強制轉換Lambda到一個委托(使用First的方法重載)。

  這裡很難結束讨論,但是我覺得必須停止。Lambda大體上分為兩類:一類建立匿名方法和委托;另一類建立表達式。表達式自成一體,并不是.NET開發者的必備知識(無疑在LINQ中已有實作)。

備注:

  此文為CodeProject上的同名文章<C# Delegates,Anonymous Methods, and Lambda Expressions>,個人覺得非常好,是以就翻一下給不想看英文的同學。由于能力有限,翻譯的不好,望大家多多包涵。

轉載請注明本文位址: C#中的委托,匿名方法和Lambda表達式

繼續閱讀