C# Lambda 表達(dá)式

從** C#3.0開始,可以使用一種新的方法把實(shí)現(xiàn)代碼賦予委托: Lambda表達(dá)式**。只要有委托參數(shù)類型的地方,就可以使用Lambda表達(dá)式。

這是前面用到的一個(gè)Lambda表達(dá)式

myTimer.Elapsed += (sender, eventArgs) =>
   {
          Console.Write(displayString[counter++ % displayString.Length]);
   };

lambda運(yùn)算符=>的左邊列出了需要的參數(shù),右邊定義了賦予lambda變量的方法的實(shí)現(xiàn)代碼。

參數(shù)

lambda表達(dá)式有幾種定義參數(shù)的方式。如果只有一個(gè)參數(shù),只需寫出參數(shù)名。
下面的例子使用參數(shù)s,因?yàn)?strong>委托定義了一個(gè)string參數(shù),那么s的類型也就是string。實(shí)現(xiàn)代碼調(diào)用String.Format()方法來返回一個(gè)字符串,在調(diào)用這個(gè)委托時(shí),字符串就打印出來。

Func<string,string> oneparam = s => String.Format("變?yōu)榇髮? {0}", s.ToUpper());
Console.WriteLine(oneparam("text"));

如果委托有多個(gè)參數(shù),就把參數(shù)名放在()里面。例如:

Func<double,double,double> twoparams = (x, y) => x * y;
Console.WriteLine(twoparams(3, 2));

這里x,y的類型是double,由Func<double,double,double>委托定義.

上面的例子里都沒有給出參數(shù)的類型,你可以給變量名添加參數(shù)類型。如果編譯器不能匹配重載后的版本,那么使用參數(shù)類型可以幫助找到匹配的委托:

Func<double,double,double> twoparams = (double x, double y) => x * y;
Console.WriteLine(twoparams(3, 2));

多行代碼

如果lambda表達(dá)式只有一條語(yǔ)句,在方法塊內(nèi)就不需要{}return語(yǔ)句,編譯器會(huì)隱式添加一條return
Func<double, double> squre = x => x * x;
添加{}return可以讓代碼更加易讀。

Func<double, double> squre = x => {
    return x * x;
    }

但是如果要在lambda表達(dá)式中添加更多語(yǔ)句,就必須使用{}return

Func<string, string> lambda = param =>{
    param += mid;
    param += "and this was added to the string.";
    return param;
    };

閉包

通過lambda表達(dá)式可以訪問表達(dá)式塊外部的變量。這稱為閉包。但使用時(shí)需要注意。

int val = 5;
Func<int, int> f = x => x + val;

Func<int, int>類型的lambda表達(dá)式需要一個(gè)int參數(shù),返回一個(gè)int,代碼訪問了外部的val變量。調(diào)用的結(jié)果應(yīng)該是x+5,但是實(shí)際上會(huì)更復(fù)雜一些。
要是在以后會(huì)修改val的值,再次調(diào)用這個(gè)lambda表達(dá)式時(shí),會(huì)使用val的新值。
如果有一個(gè)線程調(diào)用這個(gè)lambda表達(dá)式,我們可能就會(huì)不知道結(jié)果到底是多少。
對(duì)于表達(dá)式x => x + val編譯器會(huì)創(chuàng)建一個(gè)匿名類,有一個(gè)構(gòu)造方法來接收參數(shù),另一個(gè)方法實(shí)現(xiàn)并返回結(jié)果。

foreach的閉包

針對(duì)閉包,C#5.0中的foreach語(yǔ)句有了很大改變。

var values = new List<int>(){ 10, 20, 30};
var funcs = new List<Func<int>>();
foreach (var val in values){
    funcs.Add(() => val);
    }
foreach (var f in funcs){
    Console.WriteLine((f()));
}

這段代碼funcs泛型列表中添加lambda表達(dá)式,第二條foreach語(yǔ)句迭代輸出列表中引用的每個(gè)函數(shù)。其實(shí)每個(gè)函數(shù)都返回一個(gè)List<int>列表中的數(shù)字。

C#4.0或更早的版本中,會(huì)輸出30三次,而不是迭代時(shí)獲得的val變量。這個(gè)foreach的內(nèi)部實(shí)現(xiàn)有關(guān)。編譯器會(huì)從foreach語(yǔ)句創(chuàng)建一個(gè)while循環(huán)。在C#4.0中,編譯器在while循環(huán)外部定義循環(huán)變量,每次迭代中重用這個(gè)變量。因此,在循環(huán)結(jié)束時(shí),該變量的值是最后一次迭代的值。要在C#4.0中得到我們希望的結(jié)果需要在第一個(gè)foreach做如下操作:

var v = val;
funcs.Add(() => v);

C#5.0中不需要再這樣,代碼會(huì)修改為局部變量。

Lambda表達(dá)式用于匿名方法

Lambda表達(dá)式是簡(jiǎn)化匿名方法的一種方式。本文就是以這個(gè)lambda表達(dá)式開始的。

編譯器會(huì)提取這個(gè)lambda表達(dá)式,創(chuàng)建一個(gè)匿名方法,工作方式匿名方法相同。其實(shí)它會(huì)被編譯成相同或相似的CIL代碼。

下面舉一個(gè)書上的栗子,我有擴(kuò)展。
這是一個(gè)委托定義,表示一個(gè)方法,有兩個(gè)int參數(shù),返回一個(gè)int結(jié)果。
private delegate int twoparams(int p1, int p2);
這是一個(gè)以上面委托為參數(shù)的方法。

         static void Perform(twoparams tdel)
        {
            for (int i = 0; i < 5; i++)
            {
                for (int j = 0; j < 5; j++)
                {
                    int result = tdel(i, j);
                    Console.Write("f({0},{1})={2}", i, j, result);
                    if (j != 5)
                        Console.Write(" ,");
                }
                Console.WriteLine();
            }

        }

可以給這個(gè)方法傳一個(gè)委托實(shí)例,也可以是匿名方法lambda表達(dá)式
為什么可以是匿名方法lambda表達(dá)式?這是因?yàn)檫@些結(jié)構(gòu)都會(huì)被編譯為委托實(shí)例
這個(gè)方法會(huì)用一組值調(diào)用委托實(shí)例所表示的方法,并把參數(shù)輸出。

下面我創(chuàng)建一個(gè)方法來調(diào)用作為示例。

        static void Show()
        {
            twoparams test;
            test = Tdel;
            Console.WriteLine("a+b");
            Perform(((p1, p2) => p1 + p2));
            Console.WriteLine("a*b");
            Perform((
                delegate (int p1, int p2){ return p1 * p2; }
                ));
            Console.WriteLine("2*a*b");
            Perform(Tdel);
            Console.WriteLine("2*a*b-22222");//22222純屬為了方便區(qū)分,在IL中查看
            Perform(test);
        }

        private static int Tdel(int p1, int p2)
        {
            return p1*p2*2;
        }

這里用了4種方式來調(diào)用:

  1. Perform(((p1, p2) => p1 + p2));使用lambda表達(dá)式;
  2. Perform((delegate (int p1, int p2){ return p1 * p2; }));使用匿名函數(shù);
  3. Perform(Tdel);給方法傳遞一個(gè)匹配委托的方法,似乎一個(gè)方法不是一個(gè)委托,但因?yàn)槠錆M足委托的簽名,是可行的,編譯器同樣可以將其編譯成一個(gè)委托實(shí)例
  4. Perform(test);這是這個(gè)實(shí)例中唯一一個(gè)滿足方法參數(shù)的,twoparams test;創(chuàng)建一個(gè)委托實(shí)例,并給它提供一個(gè)方法test = Tdel;

主函數(shù)中運(yùn)行一下,得到如下結(jié)果:


運(yùn)行結(jié)果

用4種方法調(diào)用均可行,得到預(yù)期結(jié)果。為了驗(yàn)證它會(huì)被編譯成相同或相似的CIL代碼,我們來看看Show這個(gè)方法的中間代碼。

  private static void Show()
{
    Program.twoparams tdel = new Program.twoparams(Program.Tdel);
    Console.WriteLine("a+b");
    Program.twoparams arg_38_0;
    if ((arg_38_0 = Program.<>c.<>9__4_0) == null)
    {
        arg_38_0 = (Program.<>c.<>9__4_0 = new Program.twoparams(Program.<>c.<>9.<Show>b__4_0));
    }
    Program.Perform(arg_38_0);
    Console.WriteLine("a*b");
    Program.twoparams arg_68_0;
    if ((arg_68_0 = Program.<>c.<>9__4_1) == null)
    {
        arg_68_0 = (Program.<>c.<>9__4_1 = new Program.twoparams(Program.<>c.<>9.<Show>b__4_1));
    }
    Program.Perform(arg_68_0);
    Console.WriteLine("2*a*b");
    Program.Perform(new Program.twoparams(Program.Tdel));
    Console.WriteLine("2*a*b-22222");
    Program.Perform(tdel);
}

可以看到,使用lambda表達(dá)式和使用匿名方法得到的中間代碼非常像。最終還是給方法傳遞了一個(gè)twoparams的委托參數(shù)。而給一個(gè)符合委托參數(shù)的方法作為參數(shù)得到的中間代碼Program.Perform(new Program.twoparams(Program.Tdel));同樣如此,可以看到,我們給的是方法作為參數(shù),編譯器編譯成為委托,并且為這個(gè)委托指定了我們給的方法名。一切仿佛都變清楚了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • C++ lambda表達(dá)式與函數(shù)對(duì)象 lambda表達(dá)式是C++11中引入的一項(xiàng)新技術(shù),利用lambda表達(dá)式可以...
    小白將閱讀 85,426評(píng)論 15 117
  • 前言 人生苦多,快來 Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,282評(píng)論 9 118
  • 若要?jiǎng)?chuàng)建 Lambda 表達(dá)式,需要在 Lambda 運(yùn)算符 =>左側(cè)指定輸入?yún)?shù)(如果有),然后在另一側(cè)輸入表達(dá)...
    func_老衲姓羅閱讀 324評(píng)論 0 2
  • 應(yīng)用程序還需要操作存儲(chǔ)在其他數(shù)據(jù)源(如SQL數(shù)據(jù)庫(kù)或XML文件)中的數(shù)據(jù),甚至通過Web服務(wù)訪問它們。傳統(tǒng)上,查詢...
    CarlDonitz閱讀 607評(píng)論 0 0
  • 我是我眼瞼的囚徒 憂傷的木匠來自多雨的北方 在夜里見過候鳥一樣的姑娘 竭力挽留她在渴望的火中 讓她在夢(mèng)里、漆黑的雨...
    一位手藝人閱讀 428評(píng)論 5 15