ambda表達式的書寫方式是一個參數列表後跟「=>」記號,而後跟一個表達式或一個語句塊,即Lambda表達式的語法格式爲:express
參數列 => 語句或語句塊函數
Lambda表達式例子以下所示:this
delegate int del(int i);spa
...element
del myDelegate = x => x * x;編譯器
int j = myDelegate(5); //j = 25it
關於「參數列」,Lambda表達式的參數列能夠具備顯式的或隱式的類型。在一個具備顯式類型的參數列表中,每一個參數的類型都是顯式聲明的。在一個具備隱式類型的參數列表中,參數的類型是從Lambda表達式出現的上下文中推斷出來的——具體來講,是當Lambda表達式被轉換爲一個兼容的委託類型時,該委託類型提供了參數的類型。io
當Lambda表達式只有一個具備隱式類型的參數時,參數列表中的括號能夠省略。即:編譯
(param) => exprclass
能夠簡寫爲:
param => expr
最後,參數列中可包含任意個參數(與委託對應),若是參數列中有0個或1個以上參數,則必須使用括號括住參數列,以下:
() => Console.Write("0個參數");
i => Console.Write("1個參數時參數列中可省略括號,值爲:{0}", i);
(x, y) => Console.Write("包含2個參數,值爲:{0}:{1}", x, y);
而「語句或語句塊」中若是隻有一條語句,則能夠不用大括號括住,不然則必須使用大括號,以下所示:
i => Console.Write("只有一條語句");
i => { Console.Write("使用大括號的表達式"); };
//兩條語句時必需要大括號
i => { i++; Console.Write("兩條語句的狀況"); };
若是「語句或語句塊」有返回值時,若是隻有一條語句則能夠不寫「return」語句,編譯器會自動處理,不然必須加上,以下示例:
class Test
{
delegate int AddHandler(int x, int y);
static void Print(AddHandler add)
{
Console.Write(add(1, 3));
}
static void Main()
{
Print((x, y) => x + y);
Print((x, y) => { int v = x * 10; return y + v; });
Console.Read();
}
}
Lambda表達式是委託的實現方法,因此必須遵循如下規則:
l Lambda表達式的參數數量必須和委託的參數數量相同;
l 若是委託的參數中包括有ref或out修飾符,則Lambda表達式的參數列中也必須包括有修飾符;
咱們來看以下例子:
class Test
{
delegate void OutHandler(out int x);
static void Print(OutHandler test)
{
int i;
test(out i);
Console.Write(i);
}
static void Main()
{
Print((out int x) => x = 3);
Console.Read();
}
}
l 若是委託有返回類型,則Lambda表達式的語句或語句塊中也必須返回相同類型的數據;
l 若是委託有幾種數據類型格式而在Lambda表達式中編譯器沒法推斷具體數據類型時,則必須手動明確數據類型。
由上面可見,C# 2.0規範中提到的匿名方法規範一樣適用於Lambda表達式。Lambda表達式是匿名方法在功能行上的超集,提供了下列附加的功能:
l Lambda表達式容許省略參數類型並對其進行推斷,而匿名方法要求參數類型必須顯式地聲明。
l Lambda表達式體能夠是表達式或語句塊,而匿名方法體只能是語句塊。
l 在類型參數推導和方法重載抉擇時,Lambda表達式能夠被做爲參數傳遞。
l 以一個表達式做爲表達式體的Lambda表達式能夠被轉換爲表達式樹。
20.5.2 Lambda表達式轉換
和匿名方法表達式相似,Lambda表達式能夠歸類爲一種擁有特定轉換規則的值。這種值沒有類型,但能夠被隱式地轉換爲一個兼容的委託類型。特別地,當知足下列條件時,委託類型D兼容於Lambda表達式L:
l D和L具備相同數量的參數;
l 若是L具備顯式類型的參數列表,D中每一個參數的類型和修飾符必須和L中相應的參數徹底一致;
l 若是L具備隱式類型的參數列表,則D中不能有ref或out參數;
l 若是D具備void返回值類型,而且L的表達式體是一個表達式,若L的每一個參數的類型與D的參數一致,則L的表達式體必須是一個可接受爲statement-expression的有效表達式;
l 若是D具備void返回值類型,而且L的表達式體是一個語句塊,若L的每一個參數的類型與D的參數一致,則L的表達式體必須是一個有效語句塊,而且該語句塊中不能有帶有表達式的return語句;
l 若是D的返回值類型不是void,而且L的表達式體是一個表達式,若L的每一個參數的類型與D的參數一致,則L的表達式體必須是一個能夠隱式轉換爲D的返回值類型的有效表達式;
l 若是D的返回值類型不是void,而且L的表達式體是一個語句塊,若L的每一個參數的類型與D的參數一致,則L的表達式體必須是一個有效的語句塊,該語句塊不能有可達的終點(即必須有return語句),而且每一個return語句中的表達式都必須可以隱式轉換爲D的返回值類型。
後面的例子將使用一個範型委託F<U, T>表示一個函數,它具備一個類型爲U的參數u,返回值類型爲T:
delegate T F<U, T>(U u);
咱們能夠像在下面這樣賦值:
F<int, int> f1 = x => x + 1;
F<int, double> f2 = x => x + 1;
每一個Lambda表達式的參數和返回值類型經過將Lambda表達式賦給的變量的類型來檢測。第一個賦值將Lambda表達式成功地轉換爲了委託類型Func<int, int>,由於x的類型是int,x + 1是一個有效的表達式,而且能夠被隱式地轉換爲int。一樣,第二個賦值成功地將Lambda表達式轉換爲了委託類型Func<int, double>,由於x + 1的結果(類型爲int)能夠被隱式地轉換爲double類型。
來看以下代碼,若是這樣賦值會怎麼樣?
F<double, int> f3 = x => x + 1;
咱們運行上面的代碼,編譯器會報以下兩條錯誤:
(1)沒法將類型「double」隱式轉換爲「int」。存在一個顯式轉換(是否缺乏強制轉換?)。
(2)沒法將Lambda表達式轉換爲委託類型「F<double,int>」,緣由是塊中的某些返回類型不能隱式轉換爲委託返回類型。
其實產生一個編譯期錯誤緣由是,x給定的類型是double,x + 1的結果(類型爲double)不能被隱式地轉換爲int。
20.5.3 類型推斷
當在沒有指定類型參數的狀況下調用一個範型方法時,一個類型推斷過程會去嘗試爲該調用推斷類型參數。被做爲參數傳遞給範型方法的Lambda表達式也會參與這個類型推斷過程。
最早發生的類型推斷獨立於全部參數。在這個初始階段,不會從做爲參數的Lambda表達式推斷出任何東西。然而,在初始階段以後,將經過一個迭代過程從Lambda表達式進行推斷。特別地,當下列條件之一爲真時將會完成推斷:
l 參數是一個Lambda表達式,之後簡稱爲L,從其中未獲得任何推斷;
l 相應參數的類型,之後簡稱爲P,是一個委託類型,其返回值類型包括了一個或多個方法類型參數;
l P和L具備相同數量的參數,P中每一個參數的修飾符與L中相應的參數一致,或者若是L具備隱式類型的參數列表時,沒有參數修飾符;
l P的參數類型不包含方法類型參數,或僅包含於已經推斷出來的類型參數相兼容的一組類型參數;
l 若是L具備顯式類型的參數列表,當推斷出來的類型被P中的方法類型參數取代了時,P中的每一個參數應該具備和L中相應參數一致的類型。
l 若是L具備隱式類型的參數列表,當推斷出來的類型被P中的方法類型參數取代了而且做爲結果的參數類型賦給了L時,L的表達式體必須是一個有效的表達式或語句塊。
l 能夠爲L推斷一個返回值類型。
對於每個這樣的參數,都是經過關聯P的返回值類型和從L推斷出的返回值類型來從其上進行推斷的,而且新的推斷將被添加到累積的推斷集合中。這個過程一直重複,直到沒法進行更多的推斷爲止。
在類型推斷和重載抉擇中,Lambda表達式L的「推斷出來的返回值類型」經過如下步驟進行檢測:
l 若是L的表達式體是一個表達式,則該表達式的類型就是L的推斷出來的返回值類型。
l 若是L的表達式體是一個語句塊,若由該塊中的return語句中的表達式的類型造成的集合中剛好包含一個類型,使得該集合中的每一個類型都能隱式地轉換爲該類型,而且該類型不是一個空類型,則該類型便是L的推斷出來的返回值類型。
l 除此以外,沒法從L推斷出一個返回值類型。
做爲包含了Lambda表達式的類型推斷的例子,請考慮System.Query.Sequence類中聲明的Select擴展方法:
namespace System.Query
{
public static class Sequence
{
public static IEnumerable<S> Select<T, S>(
this IEnumerable<T> source,
Func<T, S> selector)
{
foreach (T element in source) yield return selector(element);
}
}
}