好長時間沒發技術文章了,恰好看到一篇非常詳細的Lambda文章。一邊翻譯一邊學習。題目好像有點霸氣。。
介紹
Lambda運算式是使代碼更加動態,易於擴充並且更加快速(看完本文你就知道原因了)的強有力的工具。也可以用來降低潛在的錯誤。同時可以利用靜態輸入和智能提示,就像VS裡一樣。
Lambda運算式在.net framework 3.5中提出來。並且在LINQ和ASP.NET MVC內部的一些技術中扮演了相當重要的角色。如果你考慮一下ASP.NET MVC中各類控制項的實現。你就發現。奧妙就是他們大多使用了Lambda運算式。和Lambda運算式一起,使用Html擴充方法將會使得在後台建立模型成為可能。
本文會講到如下的知識。
1.簡短的介紹-Lambda運算式是什麼,以及為什麼和匿名方法不同(之前我們使用的)
2.走近Lambda運算式的效能-在哪些情況下比起標準方法,Lambda會提高/損失效能
3.深入-Lambda運算式在MSIL代碼中是什麼樣
4.一些來自JS世界的模式映射到C#中
5.那些能夠提高效能,並且代碼看起來相當舒服的使用Lambda的情況。
6.一些我提出的新模式-當然有可能別人也提出來了。但這是我的思考結果。
如果你期望本文是一篇入門教程我可能要讓你失望了,除非你真的很優秀並且很聰明,當然我不是這種人,所以我也想提前聲明一下:為了讀懂這篇文章你可能需要C#的一些進階知識,並且對C#比較瞭解。
你應該期望本文試著解釋一些事情給你,也會解釋一些有趣的問題,至少對我來說是這樣的。最後我會展示一些實際的例子和模式,如我所說,Lambda運算式簡化了很多情況。因此寫顯式的模式很有用。
背景知識-什麼是Lambda運算式
在C#1.0中,委託被提出了,它使得傳遞函數成為可能,一句話就是委託就是強型別的函數指標,但委託比指標更強大。一般傳遞一個函數需要如下幾步。
1. 寫一個委託(就像一個類)包含傳回型別和參數類型
2. 使用委託作為某一個函數的參數類型,這樣,該函數就可以接受和委託描述的有著相同簽名的函數了
3. 將一個委託類型的函數傳遞給委託,建立一個委託執行個體。
如果聽起來很複雜,確實本來很複雜,但這是必需的。(雖然不是造火箭,但是比你認為的要更多的代碼),然而步驟三不是必需的,編譯器會為你做他,但是步驟1和2卻是必不可少的。
幸運的是C#2.0出現了泛型,現在我們也可以寫泛型類,方法,更重要的是,泛型委派,然而,直到.net framework 3.5的時候。微軟意識到實際上只有兩種泛型委派(當然有一些不同的重載),會覆蓋99%的使用方式:
1.Action 沒有任何輸入參數,也沒有輸出參數。
2.Action<t1,…t16> 需要1-16個參數,沒有輸出參數。
3.Func<t1….t16,tout>需要0-16個參數,一個輸出參數
Action和其對應的泛型版本(僅僅是一個動作,執行一些事情)返回void的時候。Func則可以返回最後一個參數指定的類型,通過這兩個委託類型,我們事實上,大部分情況下。前面提到的三步中的第一部就不用寫的。而第二步仍然需要。
那麼如果我們想要運行代碼的時候怎麼做呢。在C#2.0中問題已經可以解決了。在這個版本裡。我們可以建立委託方法,也就是一個匿名方法,然後這個文法一直未能流行起來,一個相當簡化的匿名方法的版本類似這樣:
Func<double, double> square = delegate (double x) {return x * x;}
為了提高這種文法,歡迎來到Lambda運算式的國度。首先,這個Lambda名字怎麼來的?事實上。來自於數學上的λ演算,更準確的說他是數學中一個正式的系統。用於通過變數綁定和替換來進行運算式計算,所以我們有0-N個輸入參數和一個傳回值,而在編程中,也可以沒有傳回值
我們看一下Lambda運算式的一些例子
//編譯器可以識別,然後就可以通過dummyLambda();來調用了var dummyLambda = () => { Console.WriteLine("Hallo World from a Lambda expression!"); };//可以通過類似 double y = square(25);來使用Func<double, double> square = x => x * x;//可以通過類似double z = product(9, 5);來使用Func<double, double,double> product = (x, y) => x * y;//可以通過類似printProduct(9, 5);來使用Action<double, double> printProduct = (x, y) => { Console.Writeline(x * y); };//可以通過類似var sum = dotProduct(new double[] { 1, 2, 3 }, new double[] { 4, 5, 6 });Func<double[], double[], double> dotProduct = (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;};//可以通過類似 var result = matrixVectorProductAsync(...);使用Func<double[,], double[], double[]=""> matrixVectorProductAsync = async (x, y) => {var sum = 0.0;/* do some stuff ... */return sum;};
從上面的程式碼片段裡我們可以學到一些東西
- 如果我們只有一個輸入參數,我們可以省略()
- 如果我們有一個輸入參數,並且向返回一個,那麼我們可以省略{}和return關鍵字
- 我們可以讓我們的Lambda運算式非同步執行,只要加上async關鍵字就可以了
- Var關鍵字一般都不可以用,只有在極少的情況下有才可以。
當然我們可以使用像平常那樣使用var,如果我們指定了參數類型,這是可選的。因為類型可以從委託的類型中推斷出來。而下面的情況都是錯誤的。請看下面的例子
var square = (double x) => x * x;var stringLengthSquare = (string s) => s.Length * s.Length;var squareAndOutput = (decimal x, string s) => {var sqz = x * x;Console.WriteLine("Information by {0}: the square of {1} is {2}.", s, x, sqz);};
大部分基本情況我們都知道。但是有些相當cool的東西(這使得他們在很多情況下很有用),我們考慮下面的程式碼片段
var a = 5;var multiplyWith = x => x * a;var result1 = multiplyWith(10); //50a = 10;var result2 = multiplyWith(10); //100
這是可以的。因此你可以使用其他的變數。這比你想象的要更特殊。因為這裡出現了捕獲變數。這使得出現了一個閉包,考慮下面的情況。
void DoSomeStuff(){var coeff = 10;var compute = (int x) => coeff * x;var modifier = () => {coeff = 5;};var result1 = DoMoreStuff(compute);ModifyStuff(modifier);var result2 = DoMoreStuff(compute);}int DoMoreStuff(Action computer){return computer(5);}void ModifyStuff(Action modifier){modifier();}
猜猜看這段代碼會發生什麼。首先我們建立了一個局部變數。和兩個lambda運算式,第一額Lambda運算式展示在其他局部地區裡訪問局部變數是可以的。這已經很讓人難以置信了。這意味著我們想要保護一個變數,但事實上他仍然可以在其他方法裡被訪問。而不論方法是被定義在內部還是其他類的裡。
而第二個Lambda運算式則類比了一個Lambda運算式可以修改外部域變數。這意味著我們可以在其他方法裡修改我們的局部變數,僅僅需要傳一個在對應域裡建立好的Lambda運算式就可以了。因此,我認為閉包有點魔幻的色彩了。就像並行編程一樣。可能導致未期望的結果。就像在並行編程中的競爭情況。
為了展示一下未期望的結果。歡迎期待下一篇。。