电竞比分网-中国电竞赛事及体育赛事平台

分享

Lambda表達(dá)式的前世今生(上)

 weijianian 2016-08-07


來(lái)源:匠心十年

鏈接:http://www.cnblogs.com/gaochundong/p/way_to_lambda.html


Lambda 表達(dá)式


早在 C# 1.0 時(shí),C#中就引入了委托(delegate)類型的概念。通過(guò)使用這個(gè)類型,我們可以將函數(shù)作為參數(shù)進(jìn)行傳遞。在某種意義上,委托可理解為一種托管的強(qiáng)類型的函數(shù)指針。


通常情況下,使用委托來(lái)傳遞函數(shù)需要一定的步驟:


  1. 定義一個(gè)委托,包含指定的參數(shù)類型和返回值類型。


  2. 在需要接收函數(shù)參數(shù)的方法中,使用該委托類型定義方法的參數(shù)簽名。


  3. 為指定的被傳遞的函數(shù)創(chuàng)建一個(gè)委托實(shí)例。


可能這聽(tīng)起來(lái)有些復(fù)雜,不過(guò)本質(zhì)上說(shuō)確實(shí)是這樣。上面的第 3 步通常不是必須的,C# 編譯器能夠完成這個(gè)步驟,但步驟 1 和 2 仍然是必須的。


幸運(yùn)的是,在 C# 2.0 中引入了泛型。現(xiàn)在我們能夠編寫泛型類、泛型方法和最重要的:泛型委托。盡管如此,直到 .NET 3.5,微軟才意識(shí)到實(shí)際上僅通過(guò)兩種泛型委托就可以滿足 99% 的需求:


  • Action :無(wú)輸入?yún)?shù),無(wú)返回值


  • Action :支持1-16個(gè)輸入?yún)?shù),無(wú)返回值


  • Func :支持1-16個(gè)輸入?yún)?shù),有返回值


Action 委托返回 void 類型,F(xiàn)unc 委托返回指定類型的值。通過(guò)使用這兩種委托,在絕大多數(shù)情況下,上述的步驟 1 可以省略了。但是步驟 2 仍然是必需的,但僅是需要使用 Action 和 Func。


那么,如果我只是想執(zhí)行一些代碼該怎么辦?在 C# 2.0 中提供了一種方式,創(chuàng)建匿名函數(shù)。但可惜的是,這種語(yǔ)法并沒(méi)有流行起來(lái)。下面是一個(gè)簡(jiǎn)單的匿名函數(shù)的示例:


Funcdouble, doublesquare = delegate(double x)

      {

        return x * x;

      };


為了改進(jìn)這些語(yǔ)法,在 .NET 3.5 框架和 C# 3.0 中引入了Lambda 表達(dá)式。


首先我們先了解下 Lambda 表達(dá)式名字的由來(lái)。實(shí)際上這個(gè)名字來(lái)自微積分?jǐn)?shù)學(xué)中的 λ,其涵義是聲明為了表達(dá)一個(gè)函數(shù)具體需要什么。更確切的說(shuō),它描述了一個(gè)數(shù)學(xué)邏輯系統(tǒng),通過(guò)變量結(jié)合和替換來(lái)表達(dá)計(jì)算。所以,基本上我們有 0-n 個(gè)輸入?yún)?shù)和一個(gè)返回值。而在編程語(yǔ)言中,我們也提供了無(wú)返回值的 void 支持。


讓我們來(lái)看一些 Lambda 表達(dá)式的示例:


 // The compiler cannot resolve this, which makes the usage of var impossible!

    // Therefore we need to specify the type.

    Action dummyLambda = () =>

    {

      Console.WriteLine('Hello World from a Lambda expression!');

    };

  

    // Can be used as with double y = square(25);

    Funcdouble, doublesquare = x => x * x;

   // Can be used as with double z = product(9, 5);

   Funcdouble, double, doubleproduct = (x, y) => x * y;

   // Can be used as with printProduct(9, 5);

   Actiondouble, doubleprintProduct = (x, y) => { Console.WriteLine(x * y); };

   // Can be used as with

   // var sum = dotProduct(new double[] { 1, 2, 3 }, new double[] { 4, 5, 6 });

   Funcdouble[], double[], doubledotProduct = (x, y) =>

   {

     var dim = Math.Min(x.Length, y.Length);

     var sum = 0.0;

     for (var i = 0; i != dim; i++)

       sum += x[i] + y[i];

     return sum;

   };

   // Can be used as with var result = matrixVectorProductAsync(...);

   Funcdouble[,], double[], Taskdouble[]>> matrixVectorProductAsync =

     async (x, y) =>

     {

       var sum = 0.0;

       /* do some stuff using await ... */

       return sum;

     };



從這些語(yǔ)句中我們可以直接地了解到:


  • 如果僅有一個(gè)入?yún)ⅲ瑒t可省略圓括號(hào)。


  • 如果僅有一行語(yǔ)句,并且在該語(yǔ)句中返回,則可省略大括號(hào),并且也可以省略 return 關(guān)鍵字。


  • 通過(guò)使用 async 關(guān)鍵字,可以將 Lambda 表達(dá)式聲明為異步執(zhí)行。


  • 大多數(shù)情況下,var 聲明可能無(wú)法使用,僅在一些特殊的情況下可以使用。


在使用 var 時(shí),如果編譯器通過(guò)參數(shù)類型和返回值類型推斷無(wú)法得出委托類型,將會(huì)拋出 “Cannot assign lambda expression to an implicitly-typed local variable.” 的錯(cuò)誤提示。來(lái)看下如下這些示例:



現(xiàn)在我們已經(jīng)了解了大部分基礎(chǔ)知識(shí),但一些 Lambda 表達(dá)式特別酷的部分還沒(méi)提及。


我們來(lái)看下這段代碼:


var a = 5;

   Funcint, intmultiplyWith = x => x * a;

   var result1 = multiplyWith(10); // 50

   a = 10;

   var result2 = multiplyWith(10); // 100


可以看到,在 Lambda 表達(dá)式中可以使用外圍的變量,也就是閉包。


 static void DoSomeStuff()

      {

        var coeff = 10;

        Funcint, intcompute = x => coeff * x;

        Action modifier = () =>

        {

          coeff = 5;

        };

        var result1 = DoMoreStuff(compute); // 50

        ModifyStuff(modifier);

        var result2 = DoMoreStuff(compute); // 25

      }

      static int DoMoreStuff(Funcint, intcomputer)

      {

        return computer(5);

      }

      static void ModifyStuff(Action modifier)

      {

        modifier();

      }


這里發(fā)生了什么呢?首先我們創(chuàng)建了一個(gè)局部變量和兩個(gè) Lambda 表達(dá)式。第一個(gè) Lambda 表達(dá)式展示了其可以在其他作用域中訪問(wèn)該局部變量,實(shí)際上這已經(jīng)展現(xiàn)了強(qiáng)大的能力了。這意味著我們可以保護(hù)一個(gè)變量,但仍然可以在其他方法中訪問(wèn)它,而不用關(guān)心那個(gè)方法是定義在當(dāng)前類或者其他類中。


第二個(gè) Lambda 表達(dá)式展示了在 Lambda 表達(dá)式中能夠修改外圍變量的能力。這就意味著通過(guò)在函數(shù)間傳遞 Lambda 表達(dá)式,我們能夠在其他方法中修改其他作用域中的局部變量。因此,我認(rèn)為閉包是一種特別強(qiáng)大的功能,但有時(shí)也可能引入一些非期望的結(jié)果。


var buttons = new Button[10];

  

        for (var i = 0; i )

        {

          var button = new Button();

          button.Text = (i + 1) + '. Button - Click for Index!';

          button.OnClick += (s, e) => { Messagebox.Show(i.ToString()); };

          buttons[i] = button;

        }

        //What happens if we click ANY button?!


這個(gè)詭異的問(wèn)題的結(jié)果是什么呢?是 Button 0 顯示 0, Button 1 顯示 1 嗎?答案是:所有的 Button 都顯示 10!


因?yàn)殡S著 for 循環(huán)的遍歷,局部變量 i 的值已經(jīng)被更改為 buttons 的長(zhǎng)度 10。一個(gè)簡(jiǎn)單的解決辦法類似于:


var button = new Button();

       var index = i;

       button.Text = (i + 1) + '. Button - Click for Index!';

       button.OnClick += (s, e) => { Messagebox.Show(index.ToString()); };

       buttons[i] = button;


通過(guò)定義變量 index 來(lái)拷貝變量 i 中的值。


注:如果你使用 Visual Studio 2012 以上的版本進(jìn)行測(cè)試,因?yàn)槭褂玫木幾g器與 Visual Studio 2010 的不同,此處測(cè)試的結(jié)果可能不同??蓞⒖迹篤isual C# Breaking Changes in Visual Studio 2012


表達(dá)式樹(shù)


在使用 Lambda 表達(dá)式時(shí),一個(gè)重要的問(wèn)題是目標(biāo)方法是怎么知道如下這些信息的:


  1. 我們傳遞的變量的名字是什么?


  2. 我們使用的表達(dá)式體的結(jié)構(gòu)是什么?


  3. 在表達(dá)式體內(nèi)我們用了哪些類型?


現(xiàn)在,表達(dá)式樹(shù)幫我們解決了問(wèn)題。它允許我們深究具體編譯器是如何生成的表達(dá)式。此外,我們也可以執(zhí)行給定的函數(shù),就像使用 Func 和 Action 委托一樣。其也允許我們?cè)谶\(yùn)行時(shí)解析 Lambda 表達(dá)式。


我們來(lái)看一個(gè)示例,描述如何使用 Expression 類型:


Expressionint>> expr = model => model.MyProperty;

       var member = expr.Body as MemberExpression;

       var propertyName = memberExpression.Member.Name; //only execute if member != null


上面是關(guān)于 Expression 用法的一個(gè)最簡(jiǎn)單的示例。其中的原理非常直接:通過(guò)形成一個(gè) Expression 類型的對(duì)象,編譯器會(huì)根據(jù)表達(dá)式樹(shù)的解析生成元數(shù)據(jù)信息。解析樹(shù)中包含了所有相關(guān)的信息,例如參數(shù)和方法體等。


方法體包含了整個(gè)解析樹(shù)。通過(guò)它我們可以訪問(wèn)操作符、操作對(duì)象以及完整的語(yǔ)句,最重要的是能訪問(wèn)返回值的名稱和類型。當(dāng)然,返回變量的名稱可能為 null。盡管如此,大多數(shù)情況下我們?nèi)匀粚?duì)表達(dá)式的內(nèi)容很感興趣。對(duì)于開(kāi)發(fā)人員的益處在于,我們不再會(huì)拼錯(cuò)屬性的名稱,因?yàn)槊總€(gè)拼寫錯(cuò)誤都會(huì)導(dǎo)致編譯錯(cuò)誤。


如果程序員只是想知道調(diào)用屬性的名稱,有一個(gè)更簡(jiǎn)單優(yōu)雅的辦法。通過(guò)使用特殊的參數(shù)屬性 CallerMemberName 可以獲取到被調(diào)用方法或?qū)傩缘拿Q。編譯器會(huì)自動(dòng)記錄這些名稱。所以,如果我們僅是需要獲知這些名稱,而無(wú)需更多的類型信息,則我們可以參考如下的代碼寫法:


string WhatsMyName([CallerMemberName] string callingName = null)

{

     return callingName;

}


Lambda 表達(dá)式的性能


有一個(gè)大問(wèn)題是:Lambda 表達(dá)式到底有多快?當(dāng)然,我們期待其應(yīng)該與常規(guī)的函數(shù)一樣快,因?yàn)?Lambda 表達(dá)式也同樣是由編譯器生成的。在下一節(jié)中,我們會(huì)看到為 Lambda 表達(dá)式生成的 MSIL 與常規(guī)的函數(shù)并沒(méi)有太大的不同。


一個(gè)非常有趣的討論是關(guān)于在 Lambda 表達(dá)式中的閉包是否要比使用全局變量更快,而其中最有趣的地方就是是否當(dāng)可用的變量都在本地作用域時(shí)是否會(huì)有性能影響。


讓我們來(lái)看一些代碼,用于衡量各種性能基準(zhǔn)。通過(guò)這 4 種不同的基準(zhǔn)測(cè)試,我們應(yīng)該有足夠的證據(jù)來(lái)說(shuō)明常規(guī)函數(shù)與 Lambda 表達(dá)式之間的不同了。


 class StandardBenchmark : Benchmark

    {

      static double[] A;

      static double[] B;

  

      public static void Test()

      {

        var me = new StandardBenchmark();

  

       Init();

       for (var i = 0; i 10; i++)

       {

         var lambda = LambdaBenchmark();

         var normal = NormalBenchmark();

         me.lambdaResults.Add(lambda);

         me.normalResults.Add(normal);

       }

       me.PrintTable();

      }

     static void Init()

     {

       var r = new Random();

       A = new double[LENGTH];

       B = new double[LENGTH];

       for (var i = 0; i )

       {

         A[i] = r.NextDouble();

         B[i] = r.NextDouble();

       }

     }

     static long LambdaBenchmark()

     {

       FuncdoublePerform = () =>

       {

         var sum = 0.0;

         for (var i = 0; i )

           sum += A[i] * B[i];

         return sum;

       };

       var iterations = new double[100];

       var timing = new Stopwatch();

       timing.Start();

       for (var j = 0; j )

         iterations[j] = Perform();

       timing.Stop();

       Console.WriteLine('Time for Lambda-Benchmark: t {0}ms',

         timing.ElapsedMilliseconds);

       return timing.ElapsedMilliseconds;

     }

     static long NormalBenchmark()

     {

       var iterations = new double[100];

       var timing = new Stopwatch();

       timing.Start();

       for (var j = 0; j )

         iterations[j] = NormalPerform();

       timing.Stop();

       Console.WriteLine('Time for Normal-Benchmark: t {0}ms',

         timing.ElapsedMilliseconds);

       return timing.ElapsedMilliseconds;

     }

     static double NormalPerform()

     {

       var sum = 0.0;

       for (var i = 0; i )

         sum += A[i] * B[i];

       return sum;

     }

   }



    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多