從** 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)用:
-
Perform(((p1, p2) => p1 + p2));
使用lambda表達(dá)式; -
Perform((delegate (int p1, int p2){ return p1 * p2; }));
使用匿名函數(shù); -
Perform(Tdel);
給方法傳遞一個(gè)匹配委托的方法,似乎一個(gè)方法不是一個(gè)委托,但因?yàn)槠錆M足委托的簽名,是可行的,編譯器同樣可以將其編譯成一個(gè)委托實(shí)例。 - Perform(test);這是這個(gè)實(shí)例中唯一一個(gè)滿足方法參數(shù)的,
twoparams test;
創(chuàng)建一個(gè)委托實(shí)例,并給它提供一個(gè)方法test = Tdel;
。
主函數(shù)中運(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è)委托指定了我們給的方法名。一切仿佛都變清楚了。