.NET中那些所謂的新語法之三:系統預約義委託與Lambda表達式

開篇:在上一篇中,咱們瞭解了匿名類、匿名方法與擴展方法等所謂的新語法,這一篇咱們繼續征程,看看系統預約義委託(Action/Func/Predicate)和超愛的Lambda表達式。爲了方便碼農們,.Net基類庫針對實際開發中最經常使用的情形提供了幾個預約義好的委託,這些委託能夠直接使用,無需再重頭定義一個本身的委託類型。預約義委託在.Net基類庫中使用的比較普遍,好比在Lambda表達式和並行計算中都大量地使用,須要咱們予以關注起來!html

/* 新語法索引 */

7.系統內置委託 Func / Action
8.Lambda表達式

  自 .NET Framework 3.5 (C# 3.0)以來,各類泛型委託紛涌而至,原先須要咱們程序員手動定義的一些委託如今咱們能夠直接使用預約義的委託了,大大提升了開發效率,如今咱們就首先來看看這些預約義的泛型委託。程序員

1、無返回類型的內置委託—Action

1.1 初識Action

MSDN給出的定義: 封裝一個方法,該方法不具備參數而且不返回值

  可使用此委託以參數形式傳遞方法,而不用顯式聲明自定義的委託。封裝的方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法不得具備參數,而且不得返回值。(在 C# 中,該方法必須返回 void)一般,這種方法用於執行某個操做。數據庫

  如今,咱們來看看如何使用Action委託:編程

  (1)先看看以前咱們是怎麼來使用無返回值委託的例子:數組

public delegate void ShowValue();

public class Name
{
   private string instanceName;

   public Name(string name)
   {
      this.instanceName = name;
   }

   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }

   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}

public class testTestDelegate
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      ShowValue showMethod = testName.DisplayToWindow;
      showMethod();
   }
}
View Code

  能夠清楚地看出,咱們以前要先顯式聲明瞭一個名爲 ShowValue 的委託,並將對 Name.DisplayToWindow 實例方法的引用分配給其委託實例。less

  (2)再看看有了Action委託以後咱們怎麼來達到上面的效果的例子:編程語言

public class Name
{
   private string instanceName;

   public Name(string name)
   {
      this.instanceName = name;
   }

   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }

   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}

public class testTestDelegate
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      Action showMethod = testName.DisplayToWindow;
      showMethod();
   }
}
View Code

  能夠清楚地看出,如今使用 Action 委託時,沒必要顯式定義一個封裝無參數過程的委託。ide

1.2 深刻Action

  在實際開發中,咱們常常將一個委託實例做爲一個方法的參數進行傳遞,因而咱們來看一下這個典型的場景,再經過Reflector反編譯工具查看編譯器到底幫咱們作了什麼好玩的事兒!函數式編程

  (1)首先來看一下在List集合類型的ForEach方法的定義:函數

        //
        // 摘要:
        //     對 System.Collections.Generic.List<T> 的每一個元素執行指定操做。
        //
        // 參數:
        //   action:
        //     要對 System.Collections.Generic.List<T> 的每一個元素執行的 System.Action<T> 委託。
        //
        // 異常:
        //   System.ArgumentNullException:
        //     action 爲 null。
        public void ForEach(Action<T> action);

  能夠看出,ForEach方法的參數是一個Action委託實例,也就是說是一個無返回值的委託實例。

  (2)定義一個實體類,並經過Action委託使用ForEach方法:

    public class Person
    {
        public int ID { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }
    }

    static void ActionDelegateDemo()
    {
         List<Person> personList = GetPersonList();

         personList.ForEach(new Action<Person>(delegate(Person p)
         {
                Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
          }));
     }
View Code

  能夠看出,咱們爲ForEach方法傳遞了一個Action委託的實例,本質上是一個無返回值的方法指針,遍歷輸出了每一個Person對象的信息。

  

  (3)也許有些童鞋看到上面的仍是有點不解,只要你瞭解過委託,那麼咱們能夠經過Reflector反編譯工具去看看編譯器到底作了啥事,Action委託的本質就會一如瞭然:(這裏咱們能夠先看看沒有Action的作法,是否是須要首先顯式聲明瞭一個無返回值的委託,而後是否是還要頂一個命名的無返回值的方法?

  ①將編譯好的程序集拖動到Reflector中,能夠看到如下的情形:

  ②如今分別看看編譯器爲咱們自動生成的無返回值的委託定義和方法定義:

  能夠看出,不論是自動生成的委託仍是方法,都是不帶返回值的。

  ③有了上面的分析,咱們再來看看執行的語句是怎麼被編譯的:

   能夠看出,在編譯後的代碼裏邊連new Action<Person>()都省掉了,咱們也能夠知道,在代碼中能夠更加簡化。可是,首先,咱們得了解到底編譯器是怎麼識別Action委託的。因而,按照前兩篇的思路,在反編譯後的C#代碼看不出什麼端倪的時候,切換到IL代碼一探究竟:

  由IL代碼能夠看出,仍是原來的方法,仍是原來的味道。委託仍是那個委託,執行委託仍是執行那個方法。這裏,咱們再來看看List類型的ForEach方法是怎麼使用Action委託的:

  如今,咱們能夠知道,原來所不解的東西如今終於釋懷了:在ForEach會經過一個循環遍歷依次調用委託所持有的方法,這個方法是一個符合Action委託定義的無返回值方法。至於,爲何咱們能夠省略new Action<T>(),則是編譯器爲咱們提供的一個便利。例如,咱們在使用List<Person>對象的ForEach方法時,咱們能夠這樣寫:

personList.ForEach(delegate(Person p)
{
      Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
});

  首先,因爲咱們是使用的personList這個對象(List<Person>類型),因此編譯器自動識別了泛型委託的T(即指定類型)爲Person。其次,編譯器自動將無返回值的匿名方法轉換爲了new Action<Person>對象。固然,若是是有返回值的匿名方法則會轉換爲指定類型的new Func<T>()對象,這裏由於ForEach只接受無參數的委託實例或方法,因此若是傳入了有返回值的匿名方法則會報錯。

1.3 你究竟有幾個Action可用?


  從圖中能夠看出,.NET Framework爲咱們提供了多達16個參數的Action委託定義,對於常見的開發場景已經徹底夠用了。

2、有返回類型的內置委託—Func

2.1 初識Func

MSDN給出的定義: 封裝一個具備一個參數並返回 TResult 參數指定的類型值的方法

  此委託的定義以下:

public delegate TResult Func<in T, out TResult>(T arg)

  (1)in T :此委託封裝的方法的參數類型。

  (2)out TResult :此委託封裝的方法的返回值類型。

  可使用此委託表示一種能以參數形式傳遞的方法,而不用顯式聲明自定義委託。封裝的方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具備一個經過值傳遞給它的參數,而且必須返回值。

  2.1.1 沒有Func時的使用

delegate string ConvertMethod(string inString);

public class DelegateExample
{
   public static void Main()
   {
      ConvertMethod convertMeth = UppercaseString;
      string name = "Dakota";
      Console.WriteLine(convertMeth(name));
   }

   private static string UppercaseString(string inputString)
   {
      return inputString.ToUpper();
   }
}
View Code

  2.1.2 有了Func後的使用

public class GenericFunc
{
   public static void Main()
   {
      Func<string, string> convertMethod = UppercaseString;
      string name = "Dakota";

      Console.WriteLine(convertMethod(name));
   }

   private static string UppercaseString(string inputString)
   {
      return inputString.ToUpper();
   }
}
View Code

  固然,咱們還能夠藉助匿名方法更加便捷地使用:

public class Anonymous
{
   public static void Main()
   {
      Func<string, string> convert = delegate(string s)
         { return s.ToUpper();}; 

      string name = "Dakota";
      Console.WriteLine(convert(name));   
   }
}
View Code

  能夠清楚地看出,如今使用 Func 委託時,沒必要顯式定義一個新委託並將命名方法分配給該委託。

2.2 深刻Func

  2.2.1 用法先行:爽一下

  咱們已經知道Func委託是帶指定返回值類型的委託,那麼咱們來看看在實際開發場景的一幕。仍是以剛剛那個數據集合PersonList爲例,在不少時候咱們須要對從數據庫中讀取的數據集合進行二次篩選,這時咱們可使用List集合的Select方法,咱們將一個Func委託實例做爲方法參數傳遞給Select方法,就能夠返回一個符合咱們指定條件的新數據集合。

  (1)先來看看Select方法的定義:

        //
        // 摘要:
        //     將序列中的每一個元素投影到新表中。
        //
        // 參數:
        //   source:
        //     一個值序列,要對該序列調用轉換函數。
        //
        //   selector:
        //     應用於每一個元素的轉換函數。
        //
        // 類型參數:
        //   TSource:
        //     source 中的元素的類型。
        //
        //   TResult:
        //     selector 返回的值的類型。
        //
        // 返回結果:
        //     一個 System.Collections.Generic.IEnumerable<T>,其元素爲對 source 的每一個元素調用轉換函數的結果。
        //
        // 異常:
        //   System.ArgumentNullException:
        //     source 或 selector 爲 null。
        public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

  能夠看出,Select方法中的參數採用了Func泛型委託,根據泛型委託的定義TSource和TResult分別表明要傳入的數據類型以及要返回的數據類型。

  (2)再來看看如何在程序中使用Func委託:

  首先定義一個與源數據類型不一樣的新數據類型做爲返回值類型:

    public class LitePerson
    {
        public string Name { get; set; }
    }

  ①標準定義版:

            List<Person> personList = GetPersonList();
            
            IEnumerable<LitePerson> litePersonList = personList.Select<Person, LitePerson>(
                new Func<Person, LitePerson>
                (
                    delegate(Person p)
                    {
                        return new LitePerson() { Name = p.Name };
                    }
                )
            );    

  ②嘻哈簡化版:藉助編譯器提供的自動識別,簡化咱們的代碼

            IEnumerable<LitePerson> litePersonList = personList.Select(
                delegate(Person p)
                {
                    return new LitePerson() { Name = p.Name };
                }
            );

  ③絕逼懶人版:藉助匿名類和泛型能夠大大簡化咱們的代碼

            var liteList = personList.Select(delegate(Person p)
            {
                return new { Name = p.Name, AddDate = DateTime.Now };
            });

  (3)調試運行能夠獲得如下結果:

  2.2.2 原理爲王:探一次

  (1)經過Reflector反編譯,咱們再來看看編譯器幫咱們生成的東東:

  (2)看看自動生成的委託和方法的定義:

  相信通過上節Action的詳細分析,這裏你們應該也能夠觸類旁通了解編譯器幫咱們到底作了什麼事兒了,這裏我就再也不贅述了,後面也不會再贅述此方面的東東(爲了節省頁面大小)。

  固然,和Action相似,.NET基類庫爲咱們也提供了多達16個輸入參數的Func委託,可是,輸出參數卻只有1個。

3、返回bool類型的內置委託—Predicate

3.1 初識Predicate

  通過了Func的瞭解,咱們能夠知道接下來的這兩個Predicate和Comparison其實都屬於有返回值類型的委託,他們不過是兩個具體的特殊實例而已(一個返回bool類型,一個返回int類型)。

MSDN給出的定義: 表示定義一組條件並肯定指定對象是否符合這些條件的方法

  它的定義很簡單:(這裏就再也不對其進行解釋了)

public delegate bool Predicate<in T>(T obj)

  此委託由 Array 和 List<T> 類的幾種方法使用,經常使用於在集合中搜索元素。

3.2 深刻Predicate

  因爲Predicate委託經常使用於在集合中搜索元素,那麼咱們就來看看如何使用Predicate委託來進行元素的搜索。因而,咱們將目光轉到List集合的FindAll方法,相信大部分童鞋都用過這個方法。

  (1)先來看看FindAll的定義:

        //
        // 摘要:
        //     檢索與指定謂詞定義的條件匹配的全部元素。
        //
        // 參數:
        //   match:
        //     System.Predicate<T> 委託,用於定義要搜索的元素應知足的條件。
        //
        // 返回結果:
        //     若是找到,則爲一個 System.Collections.Generic.List<T>,其中包含與指定謂詞所定義的條件相匹配的全部元素;不然爲一個空
        //     System.Collections.Generic.List<T>。
        //
        // 異常:
        //   System.ArgumentNullException:
        //     match 爲 null。
        public List<T> FindAll(Predicate<T> match);

  (2)再來看看FindAll的實現:

  (3)如今咱們來用一下Predicate委託:仍是以那個PersonList集合爲例,假如咱們要篩選出Age>20的Person,咱們就可使用FindAll方法。如今咱們來寫一下這個委託:(後面咱們會用Lambda表達式來簡寫,那才叫一個爽!)能夠看出,關鍵點在於:delegate(Person p) { return p.Age > 20; }這一句上,傳入參數是Person類型的對象,返回的是一個比較結果即bool值。

            List<Person> personList = GetPersonList();

            List<Person> agedList = personList.FindAll(
                new Predicate<Person>(delegate(Person p) 
                    { 
                        return p.Age > 20; 
                    }
                )
            );
View Code

4、返回int類型的內置委託—Comparison

4.1 初識Comparison

MSDN給出的定義:表示比較同一類型的兩個對象的方法

  它的定義也很簡單:

public delegate int Comparison<in T>(T x, T y)

  T是要比較的對象的類型,而返回值是一個有符號整數,指示 x 與 y 的相對值,以下表所示:

含義

小於 0

x 小於 y。

0

x 等於 y。

大於 0

x 大於 y。

  此委託由 Array 類的 Sort<T>(T[], Comparison<T>) 方法重載和 List<T> 類的 Sort(Comparison<T>) 方法重載使用,用於對數組或列表中的元素進行排序

4.2 深刻Comparison

  因爲Comparison委託經常使用於在集合中進行排序,那麼咱們就來看看如何使用Comparison委託來進行元素的排序。因而,咱們將目光轉到List集合的Sort方法,相信大部分童鞋也都用過這個方法。

  (1)老慣例,仍是先看看Sort方法的定義:

        //
        // 摘要:
        //     使用指定的 System.Comparison<T> 對整個 System.Collections.Generic.List<T> 中的元素進行排序。
        //
        // 參數:
        //   comparison:
        //     比較元素時要使用的 System.Comparison<T>。
        //
        // 異常:
        //   System.ArgumentNullException:
        //     comparison 爲 null。
        //
        //   System.ArgumentException:
        //     在排序過程當中,comparison 的實現會致使錯誤。 例如,將某個項與其自身進行比較時,comparison 可能不返回 0。
        public void Sort(Comparison<T> comparison);

  (2)再來看看Sort方法的實現:

  能夠看出,這裏雖然使用Comparison委託但最終仍是轉換成了Comparer比較器,再次調用重載的Array.Sort靜態方法進行排序。

  (3)如今咱們來用一下Comparison委託:仍是以那個PersonList集合爲例,假如咱們要以Age爲條件進行降序排列,咱們應該怎麼來寫這個委託呢?

List<Person> personList = GetPersonList();

personList.Sort(delegate(Person p1, Person p2)
{
      return p2.Age - p1.Age;
});

personList.ForEach(delegate(Person p)
{
      Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
});
View Code

  實現的效果以下圖所示:

  那麼,若是是要進行升序排列呢?只須要改一下:return p2.Age-p1.Age; 更改一下被減數和減數的位置,便可完成升序和降序的切換。

personList.Sort(delegate(Person p1, Person p2)
{
      return p1.Age - p2.Age;
});
View Code

5、Lambda表達式:[ C# 3.0/.NET 3.x 新增特性 ]

  回顧,發現上面的代碼,須要傳一個 匿名方法 ,寫起來特別彆扭。因而咱們很想知道可否有簡化的語法呢?微軟告訴我們:Of Course,必須有,它就是Lambda表達式。Lambda表達式是比匿名方法更簡潔的一種匿名方法語法。

Lambda來源:1920年到1930年期間,數學家Alonzo Church等人發明了Lambda積分。Lambda積分是用於表示函數的一套系統,它使用希臘字母Lambda(λ)來表示無名函數。近年來,函數式編程語言(如Lisp)使用這個術語來表示能夠直接描述函數定義的表達式,表達式再也不須要有名字了。

5.1 初識Lambda表達式lambda  5.1.1 Lambda表達式要點

    ①Lambda表達式中的參數列表(參數數量、類型和位置)必須與委託相匹配

    ②表達式中的參數列表不必定須要包含類型,除非委託有ref或out關鍵字(此時必須顯示聲明);

    ③若是沒有參數,必須使用一組空的圓括號

  5.1.2 Lambda使用示例

        static void LambdaDemo()
        {
            List<Person> personList = GetPersonList();
            Console.WriteLine("--------------------標準預約義委託--------------------");
            Console.WriteLine("Standard Action Delegate Show:");
            personList.ForEach(new Action<Person>(delegate(Person p)
                {
                    Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
                })
            );

            Console.WriteLine("Simple Action Delegate Show:");
            personList.ForEach(delegate(Person p)
            {
                Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
            });
            Console.WriteLine("--------------------Lambda表達式--------------------");
            Console.WriteLine("Lambda Expression Show:");
            personList.ForEach(p =>
                Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age));

            Console.WriteLine("FindAll:");
            var dataList = personList.FindAll(p => p.Age > 20);
            foreach (var item in dataList)
            {
                Console.WriteLine(item.ID + "-" + item.Name + "-" + item.Age);
            }

            Console.WriteLine("Sort:");
            personList.Sort((p1, p2) => p1.Age - p2.Age);

            Console.WriteLine("Select:");
            var selectList = personList.Select(p => new LitePerson() { Name = p.Name });
            foreach(var item in selectList)
            {
                Console.WriteLine(item.Name);
            }
        }
View Code

   調試運行的結果以下:

  5.1.3 Lambda本質探析

  (1)以上述案例中的Sort方法爲例:personList.Sort((p1, p2) => p1.Age - p2.Age);

  (2)經過反編譯工具,能夠看到實際上是聲明瞭一個Comparison委託實例:

  (3)如今,咱們來分析一下具體的步湊:有了前面的基礎,如今再來看就輕鬆了許多,So Easy!

    ①編譯器自動生成了一個Comparison委託:

    ②編譯器幫咱們建立了一個符合Comparison委託簽名的靜態方法:

    ③實例化Comparison委託變量,並將方法指針傳入該委託;

    ④調用List<T>實例的Sort方法,並傳入Comparison委託實例;

    其中,前面兩步①和②能夠經過反編譯後的C#代碼獲知,然後面兩步③和④則須要經過IL代碼來分析,前面已經介紹過相關,這裏就再也不贅述。

5.2 回顧Lambda進化史

  前面瞭解了Lambda是什麼,這裏咱們來回顧一下Lambda的演化過程。

  從演化過程能夠知道,編譯器在愈來愈智能地幫咱們作着更多的事兒,而咱們卻在享受着編譯器帶來的便利沉浸在高效的開發效率中,變得愈來愈「懶」了。

5.3 語句Lambda

  Lambda表達式有兩種類型:一是Lambda表達式,二是語句Lambda。

  那麼,語句Lambda和表達式Lambda到底有何區別?

ANSWER:語句Lambda 和 表達式Lambda 的區別在於,前者在 =>右邊有一個語句塊(大括號),然後者只有一個表達式(沒有return 和大括號)。
EXAMPLES:
(1)表達式Lambda:
list.FindAll(d => d.Id > 2);// goes to
list.ForEach(d => Response.Write(d.ToString() + "<br/>"));

(2)語句Lambda:

list.ForEach(d => { if (d.Id > 2) { Response.Write(d.ToString() + "<br/>"); } });

  能夠看出,語句Lambda的右側有一個語句塊,在這個大括號內的語句可能會有多條。

參考文章

  (1)金旭亮,《C#面向對象程序設計》,教案6-委託與事件講義:http://download.csdn.net/detail/bitfan/3324733

  (2)MSDN,泛型委託(C#編程指南):http://msdn.microsoft.com/zh-cn/library/sx2bwtw7.aspx

  (3)min,《泛型委託在項目中的應用》:http://www.cnblogs.com/ASPNET2008/archive/2010/04/05/1704405.html

  (4)MSDN,Lambda表達式(C#編程指南):http://msdn.microsoft.com/zh-cn/library/bb397687.aspx

  (5)張龍豪,《Lambda表達式詳解》:http://www.cnblogs.com/knowledgesea/p/3163725.html

附件下載

  NewGrammerDemos v1.2:http://pan.baidu.com/s/1gdxi39D

 

相關文章
相關標籤/搜索