C#委託,匿名方法和Lambda運算式(譯)

來源:互聯網
上載者:User

在.net中,委託,匿名方法和Lambda運算式是三個很容易讓人混淆的概念.以下代碼或許可見一斑:對First的調用中,哪些(個)會被編譯?哪些(個)將會返回我們所期待的答案?(ID號為5的Customer).事實上,答案就是:所有的6種方法不令都將編譯,而且它們都能夠返回正常的customer,它們在功能上是相同的.如果你還在問自己:為什麼是這樣呢?那麼,這篇文章將為你解答.

class Customer
{
    public int ID { get; set; }
    public static bool Test(Customer x)
    {
        return x.ID == 5;
    }
}
...
List<Customer> custs = new List<Customer>();
custs.Add(new Customer() { ID = 1 });
custs.Add(new Customer() { ID = 5 });

custs.First(new Func<Customer, bool>(delegate(Customer x) { return x.ID == 5; }));
custs.First(new Func<Customer, bool>((Customer x) => x.ID == 5));
custs.First(delegate(Customer x) { return x.ID == 5; });
custs.First((Customer x) => x.ID == 5);
custs.First(x => x.ID == 5);
custs.First(Customer.Test);

一,什麼是委託?

舉例來說吧,假如有一個購物籃類(Shoppingcart)用於處理顧客(Customer)的訂單(Order).經理決定對超過一定金額或購買量等的顧客打折.於是,你必須使用他們制訂的策略來計算訂單.這並不是什麼難事:你簡單的聲明了一個變數來儲存折扣將在計算訂單金額時調用它.

class Program {     static void Main(string[] args)     {         new ShoppingCart().Process();     } }  class ShoppingCart {     public void Process()     {         int magicDiscount = 5;         // ...     }}

然而,第二天,精明的經理又決定要根據一天當中的時間來打折.呵呵,這也不難,你只需要簡單的改變一下代碼就可以了:

class ShoppingCart {     public void Process()     {         int magicDiscount = 5;         if (DateTime.Now.Hour < 12)         {             magicDiscount = 10;         }     } }

接下來幾天裡,經理一再增加或修改折扣的計算方法.暈倒!我怎麼才能處理和維護這近似荒謬的邏輯呀?其實,你所需要做的只是"移交",或者稱之為"委託"責任給別人就可以了.在.net中,就有這麼一種機制,我不說你也一定猜到了,那就是"委託".

二,委託

如果大家有C/C++背景的話,那麼最佳描述委託的就是函數指標.而對於一般人來說,可以認為它是一種把方法像普通參數一樣傳遞的方式.例如,以下三行代碼體現了同樣的基本原則:向Process方法傳遞一條資料,而不使用它.

// passing an integer value for the Process method to use Process( 5 ); // passing a reference to an ArrayList object for the Process method to use Process( new ArrayList() ); // passing a method reference for the Process method to call Process( discountDelegate ); 

那麼,上面的discountDelegate是什麼呢?我們該怎麼去建立它呢?Process方法又是怎樣使用它的呢?首先,我們需要做的是聲明一個delegate類型就像我們聲明一個類一樣.

delegate int DiscountDelegate(); 

這名代碼讓我們擁有了一個DiscountDelegate委託,我們可以像類和結構一樣使用它.這個委託沒有參數,只有一個int型的傳回值.和類一樣,在建立它的任何執行個體之前,我們不能使用它.在執行個體化委託的時候,需要特別注意:委託只是某方法的一個引用而已,即使DiscountDelegate沒有任何建構函式,當執行個體化的時候,會有一個隱藏的建構函式(無參數但返回int值).怎樣給這個建構函式一個方法呢?OK,在.net中,我們只需要簡單的輸入想要調用的方法的名稱就可以了,省略了方法名後面的括弧.

DiscountDelegate discount = new DiscountDelegate(class.method); 

在進一步探討之前,讓我們回過頭來看看之前的例子.我們將增加一個Calculator 類來協助我們計算折扣,並設計一些方法來提供給委託引用.

delegate int DiscountDelegate();  class Program {     static void Main(string[] args)     {         Calculator calc = new Calculator();         DiscountDelegate discount = null;         if (DateTime.Now.Hour < 12)         {             discount = new DiscountDelegate(calc.Morning);         }         else if (DateTime.Now.Hour < 20)         {             discount = new DiscountDelegate(calc.Afternoon);         }         else         {             discount = new DiscountDelegate(calc.Night);         }         new ShoppingCart().Process(discount);     } }  class Calculator {     public int Morning()     {         return 5;     }     public int Afternoon()     {         return 10;     }     public int Night()     {         return 15;     } }  class ShoppingCart {     public void Process(DiscountDelegate discount)     {         int magicDiscount = discount();         // ...     } }

正如我們所看到的,我們在Calculator 類中為每一個折扣的計算邏輯建立了一個方法.在Main方法中,我們為Calculator類和DiscountDelegate 委拖分別建立了一個執行個體,它們將共同為我們即將調用的目標方法服務.

到目前為止,我們不再為Process 方法裡的計算邏輯而擔憂啦,我們可以簡單的調用委託來完成.不過要記住:我們並不關心這個委託是如何,何時建立的.我們只是在需要它的時候像調用其它方法一樣調用它.可見,委託也可以理解為延緩方法執行.真正的計算方法是在我們調用discount()方法的時候才得以執行.再看看上面的代碼,還有一些重複的地方.在Calculator 類中,我們是不是可以用一個方法來提供所有傳回值?當然可以,那就讓我們一起來改進吧.

delegate int DiscountDelegate();  class Program {     static void Main(string[] args)     {         new ShoppingCart().Process(new DiscountDelegate(Calculator.Calculate));     } }  class Calculator {     public static int Calculate()     {         int discount = 0;         if (DateTime.Now.Hour < 12)         {             discount = 5;         }         else if (DateTime.Now.Hour < 20)         {             discount = 10;         }         else         {             discount = 15;         }         return discount;     } }  class ShoppingCart {     public void Process(DiscountDelegate discount)     {         int magicDiscount = discount();          // ...     } }

這下好啦,我們做到啦.靜態Calculate 方法讓類變得清爽了很多.Main方法也不再有那麼多的對DiscountDelegate 的引用啦.好,現在,我們已經對委託有所瞭解了.

三,我們需要這樣的功能!

在.NET 2.0中,引入了泛型的概念.MS小心翼翼的通過提供Action<T>類來邁向泛型委拖.然後,我認為,一段時間之後,它被我們大多數人所遺忘了.後來,到了3.5,MS很友好的為我們預定義了一些常見的委託來使用,所以我們不再需要不斷的定義是我們自己的委託啦.他們還擴充了Action並增加了Func.Action和Func唯一的不同就是前者沒傳回值而後者有.

也就是說,我們不再需要去聲明DiscountDelegate ,我們可以使用Func<int>來代替它.為們示範參數的工作方法,讓我們假設經理再一次改變了折扣的計算邏輯:現在,我們需要記錄一個特別的折扣.這也是小菜一碟,我們只需要在Calculate 方法中調用一個boolean類型的值就可以啦.

這樣,我們的原本無參委託就變成了Func<bool, int>的形式了.注意,現在的Calculate 方法有一個boolean類型的參數,我們在調用discount的時候要帶上它.

class Program {     static void Main(string[] args)     {         new ShoppingCart().Process(new Func<bool, int>(Calculator.Calculate));     } }  class Calculator {     public static int Calculate(bool special)     {         int discount = 0;         if (DateTime.Now.Hour < 12)         {             discount = 5;         }         else if (DateTime.Now.Hour < 20)         {             discount = 10;         }         else if (special)         {             discount = 20;         }         else         {             discount = 15;         }         return discount;     } }  class ShoppingCart {     public void Process(Func<bool,int> discount)     {         int magicDiscount = discount(false);         int magicDiscount2 = discount(true);     } }

這主意不錯,我們又節省了幾行代碼,不是嗎?當然不是,如果利用類型介面,我們還可以節省更多的代碼和時間..net允許我們完全省略Func<bool, int>的建立過程,只要這個方法具有和我們期望的委託同樣的參數和傳回值.

// works because Process expects a method that takes a bool and returns int new ShoppingCart().Process(Calculator.Calculate); 

基於這一點,我們通過省略自訂委託並在隨後略去Func委託的直接建立,又省下了不少代碼.還有什麼方法可以減少代碼量的嗎?當然有,因為我們才說了本文的一半.

四,匿名方法

匿名方法允許我們聲明一個沒有名字的方法.在後台,有一個名叫'normal'的方法.然而,在代碼中我們不能顯式的調用這個方法,匿名方法只能在使用委託,並且使用Delegate關鍵字建立時建立和使用.如:

class Program {     static void Main(string[] args)     {         new ShoppingCart().Process(             new Func<bool, int>(delegate(bool x) { return x ? 10 : 5; }         ));     } }

從上面我們可以看到:我們完全不用Calculator 類.你可以輸入更多的小邏輯到花括弧的分枝中,就像在其它方法中使用的一樣.如果你覺得難以理解這段代碼的工作過程,就請假設聲明delegate(bool x) 是一個方法的簽名而不是一個委託的關鍵字.把這段代碼放到一個類中,delegate(bool x) { return 5; }是一個特別的邏輯計算的方法的聲明(本應有傳回值類型的).這方面來理解,就好辦啦.delegate 只是使原本方法的名稱隱藏起來了而已.

好,我相信到目前為止,我們可以省略更多的代碼啦.自然的,我們可以忽略對Func委託的顯示聲明,當我們使用delegate關鍵字的時候,.net會替我們處理好一切.

class Program {     static void Main(string[] args)     {         new ShoppingCart().Process(           delegate(bool x) { return x ? 10 : 5; }         );     } }

在.net中,當方法期望一個委託作為參數並響應事件的時候,才能看見匿名方法的真正威力.以前,我們必須為每一個可能的動作建立方法.而現在,我們只需要一行語句就搞定了,而且不會"汙染"命名空間.

// creates an anonymous comparer custs.Sort(delegate(Customer c1, Customer c2) {     return Comparer<int>.Default.Compare(c1.ID, c2.ID); });  // creates an anonymous event handler button1.Click += delegate(object o, EventArgs e)                       { MessageBox.Show("Click!"); }; 
五,Lambda運算式(引自MSDN)Lambda運算式是一個能夠包含運算式和語句,並且能夠用來建立委託或運算式樹狀架構類型的匿名函數.我們需要理解"用來建立委託"部分,Lambda運算式到底扮演著怎麼的著色呢?好,其實上,運算式和運算式樹狀架構已超出了本方的闡述範圍.我們只需要知道:運算式就是.net程式中所謂的資料或對象本身的代碼而已.運算式樹狀架構是一種其它代碼可以"詢問"的表達邏輯的方式.當lambda 運算式被轉換成運算式樹狀架構時,編輯器並沒產生新的IL代碼,運算式樹狀架構和lambda 運算式代表了相同的邏輯.

我們所需要關注的是:用lambda 運算式代替匿名方法並增加新的特性.回顧我們最後的那個例子,我們已經在最初建立的基礎上去掉了很多代碼,將把折扣計算邏輯寫到了一行上.

class Program {     static void Main(string[] args)     {         new ShoppingCart().Process(             delegate(bool x) { return x ? 10 : 5; }         );     }}

你相信我們還能讓它變得更簡短一些嗎?Lambda 運算式使用"=>"符號來代表"參數被傳遞到運算式中",編譯器遇到這個符號的時候會允許我們忽略參數的類型,並且為我們推斷出類型來.如果有兩個或兩個以上的參數,可以使用括弧:(x,y) =>. 如果只有一個,那就這樣:x =>.

static void Main(string[] args) {     Func<bool, int> del = x => x ? 10 : 5;     new ShoppingCart().Process(del); } // even shorter... static void Main(string[] args) {     new ShoppingCart().Process(x => x ? 10 : 5); }

對,就是這樣的.x被推斷成boolean,傳回值類型也一樣.因為Process 有一個Func<bool, int>類型的參數,如果你想像前面一樣執行這段代碼,我們只需要增加分枝就可以了.

static void Main(string[] args) {     new ShoppingCart().Process(     x => {         int discount = 0;         if (DateTime.Now.Hour < 12)         {             discount = 5;         }         else if (DateTime.Now.Hour < 20)         {             discount = 10;         }         else if(x)         {             discount = 20;         }         else         {              discount = 15;         }         return discount;     }); } 
六,最後的事

在是否使用分枝的問題上,一個很大的不同.當你使用的時候,相當於是建立了一個"語句式的lambda'",反之,它就是一個"運算式式的lambda",前者可以根據分枝需要執行多行語句,但不能建立運算式樹狀架構.當我們在使用IQueryable 介面的時候,有可能會陷入這個問題中, 下面的例子展示了這一問題"

List<string> list = new List<string>(); IQueryable<string> query = list.AsQueryable(); list.Add("one"); list.Add("two"); list.Add("three");  string foo = list.First(x => x.EndsWith("o")); string bar = query.First(x => x.EndsWith("o")); // foo and bar are now both 'two' as expected foo = list.First(x => { return x.EndsWith("e"); }); //no error bar = query.First(x => { return x.EndsWith("e"); }); //error bar = query.First((Func<string,bool>)(x => { return x.EndsWith("e"); })); //no error 

第二次計算bar值的時候發生了編輯時錯誤.這是因為IQueryable.First 期望一個運算式做為參數,然而,運算式方法List<T>.First 則期望一個委託.你可以強制將lambda 運算式的值轉換為delegate,像上面第三次一樣.

寫到這裡,其實還有很多未言的東西,但是我想我必須要結束本文了.lambda 基本上被分為兩類:一類是建立匿名方法和委託,另一類是建立運算式.

七,總結

希望這編文章可以達到解答最前面的六個易混淆的有關委託,匿名方法和lambda運算式調用問題的目標.

<完>

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.