聲明
本文內(nèi)容來自微軟 MVP solenovex 的視頻教程——真會C#? - 第4章 委托挨稿、事件、Lambda表達(dá)式(完結(jié))寇壳,大致和第 4 課—— 4.3 4.4 Lambda表達(dá)式 對應(yīng)。可在 GitHub 中查看 C# 視頻教程的配套PPT
本文主要包括以下內(nèi)容:
- 顯式指定 Lambda 表達(dá)式的參數(shù)類型
- 捕獲外部變量
- Lambda 表達(dá)式 vs 本地方法
- 匿名方法
Lambda 表達(dá)式
Lambda表達(dá)式其實就是一個用來代替委托實例的未命名的方法鲤桥,編譯器會把Lambda表達(dá)式轉(zhuǎn)化為以下二者之一:
- 一個委托實例
- 一個表達(dá)式樹(expression tree),類型是 Expression<TDelegate>渠概,它表示了可遍歷的對象模型中 Lambda 表達(dá)式里面的代碼茶凳。它允許 Lambda 表達(dá)式延遲到運行時再被解釋
delegate int Transformer (int i);
Transformer sqr = x => x * x;
Console.WriteLine (sqr(3)); // 9
實際上,編譯器會通過編寫一個私有方法來解析這個 Lambda 表達(dá)式播揪,然后把表達(dá)式的代碼移動到這個方法里贮喧。
Lambda表達(dá)式的形式,(參數(shù))=> 表達(dá)式或語句塊猪狈,(parameters) => expression-or-statement-block
其中如果只有一個參數(shù)并且類型可推斷的話箱沦,那么參數(shù)的小括號可以省略
Lambda 表達(dá)式與委托,每個 Lambda 表達(dá)式的參數(shù)對應(yīng)委托的參數(shù)雇庙,表達(dá)式的類型對應(yīng)委托的返回類型谓形。
x => x * x;
delegate int Transformer (int i);
Lambda 表達(dá)式的代碼也可以是語句塊。x => { return x * x; };
Lambda 表達(dá)式通常與 Func 和 Action 委托一起使用疆前,
Func<int,int> sqr = x => x * x;
Func<string,string,int> totalLength = (s1, s2) => s1.Length + s2.Length;
int total = totalLength ("hello", "world"); // total is 10;
顯式指定 Lambda 表達(dá)式的參數(shù)類型
void Foo<T> (T x) {}
void Bar<T> (Action<T> a) {}
Bar (x => Foo (x)); // What type is x?
Bar ((int x) => Foo (x));
Bar<int> (x => Foo (x)); // Specify type parameter for Bar
Bar<int> (Foo); // As above, but with method group
捕獲外部變量
Lambda 表達(dá)式可以引用本地的變量和所在方法的參數(shù)寒跳。
static void Main()
{
int factor = 2;
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3)); // 6
}
被捕獲的變量
被 Lambda 表達(dá)式引用的外部變量叫做被捕獲的變量(captured variables)。捕獲了外部變量的 Lambda 表達(dá)式叫做閉包峡继。被捕獲的變量是在委托被實際調(diào)用的時候才被計算冯袍,而不是在捕獲的時候。
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Lambda 表達(dá)式本身也可以更新被捕獲的變量碾牌。
int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
Console.WriteLine (seed); // 2
被捕獲的變量的生命周期會被延長到和委托一樣康愤。
static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
}
Lambda 表達(dá)式內(nèi)的本地變量
在 Lambda 表達(dá)式內(nèi)實例化的本地變量對于委托實例的每次調(diào)用來說都是唯一的。
static Func<int> Natural()
{
return() => { int seed = 0; return seed++; };
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 0
}
捕獲迭代變量
當(dāng)捕獲 for 循環(huán)的迭代變量時舶吗,C# 會把這個變量當(dāng)作是在循環(huán)外部定義的變量征冷,這就意味著每次迭代捕獲的都是同一個變量。
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
actions [i] = () => Console.Write (i);
foreach (Action a in actions) a(); // 333
Action[] actions = new Action[3];
int i = 0;
actions[0] = () => Console.Write (i);
i = 1;
actions[1] = () => Console.Write (i);
i = 2;
actions[2] = () => Console.Write (i);
i = 3;
foreach (Action a in actions) a(); // 333
如何解決每次迭代捕獲的都是同一個變量
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
int loopScopedi = i;
actions [i] = () => Console.Write (loopScopedi);
}
foreach (Action a in actions) a(); // 012
注意:foreach誓琼,C#4检激,和 C#5+ 的區(qū)別。
Action[] actions = new Action[3];
int i = 0;
foreach (char c in "abc")
actions [i++] = () => Console.Write (c);
foreach (Action a in actions) a(); // ccc in C# 4.0
Lambda 表達(dá)式 vs 本地方法
本地方法是 C#7 的一個新特性腹侣。它和 Lambda 表達(dá)式在功能上有很多重復(fù)之處叔收,但它有三個優(yōu)點:
- 可以簡單明了的進(jìn)行遞歸
- 無需指定委托類型(那一堆代碼)
- 性能開銷略低一點
本地方法效率更高是因為它避免了委托的間接調(diào)用(需要 CPU 周期,內(nèi)存分配)傲隶。本地方法也可以訪問所在方法的本地變量饺律,而且無需編譯器把被捕獲的變量 hoist 到隱藏的類。
匿名方法
匿名方法 vs Lambda 表達(dá)式
匿名方法和Lambda表達(dá)式很像跺株,但是缺少以下三個特性:
- 隱式類型參數(shù)
- 表達(dá)式語法(只能是語句塊)
- 編譯表達(dá)式樹的能力复濒,通過賦值給 Expression<T>
delegate int Transformer (int i);
Transformer sqr = delegate (int x) {return x * x;};
Console.WriteLine (sqr(3));
Transformer sqr = (int x) => {return x * x;};
// Or simply:
Transformer sqr = x => x * x;
其它
捕獲外部變量的規(guī)則和 Lambda 表達(dá)式是一樣的脖卖。但匿名方法可以完全省略參數(shù)聲明,盡管委托需要參數(shù)巧颈。public event EventHandler Clicked = delegate { };
這就避免了觸發(fā)事件前的null檢查畦木。
// Notice that we omit the parameters:
Clicked += delegate { Console.WriteLine ("clicked"); };
參考
Lambda expressions (C# Programming Guide)
=> operator (C# reference)
Anonymous functions (C# Programming Guide)