【治病之旅】Element-UI的resetFields()方法不生效的原因及解決方法
Lambda表示式
Lambada表示式是一種可以替代委託例項的匿名方法。編譯器會立即將Lambda表示式轉換為一下兩種形式之一:
- 一個委託例項
- 一個型別為Expression
的表示式樹(這個後面將)
匿名方法
上面說Lambada是一種匿名方法,那麼就要先了解一下什麼是匿名方法
匿名方法是C#2.0引入的特性
匿名方法的寫法實在delegate關鍵字後面跟上引數的宣告(可選),然後是方法體
using System; class Program { delegate void Example(); static void Main(string[] args) { // Example e = delegate(){ Console.WriteLine("一個匿名方法的實現"); }; // 如果沒有引數,可以省略引數的括號 Example e = delegate { Console.WriteLine("一個匿名方法的實現"); }; e(); } }
這裡聲明瞭一個匿名方法,匿名方法解決的問題,是有時候想用委託,就必須需要一個方法,但是這個方法只是在這個委託中使用一下子,不需要在其他地方複用,於是引入了匿名方法
delegate(){ Console.WriteLine("一個匿名方法的實現"); };
匿名方法的寫法其實與普通方法並無異樣,只是用delegate關鍵字在前標註,省略掉方法名(如果不好理解,可以理解成方法名為delegate,沒有引數可以省略括號,只能用於註冊進委託)
匿名方法使用情況不多,因為C#3.0引入的Lambda更加強大,也是後面要講的重點
匿名方法目前最廣泛的用法,是用於宣告空事件處理器的事件
public event EventHandler Clicked =delegate { };
Clicked事件不會進行任何操作,因為沒有定義任何操作,但是Clicked不為空,不會拋異常,在使用者層面,就是點選了某個按鈕後沒有任何變化,但是如果Clicked為空,就會拋異常
完全省略引數的宣告是匿名方法獨有的特性,即使委託需要這些引數宣告,如上面宣告空事件處理器的事件,EventHandler
其實需要一個object
的引數和一個EventArgs
型別的引數
Lambda表示式
Lambda是一種更強大匿名方法,前面講了匿名方法,先來看看Lambda如何替代匿名方法
using System; class Program { delegate void Example(); static void Main(string[] args) { // Example e = () => { Console.WriteLine("一個Lambda表示式"); }; // 當方法體只有一句時可以省略大括號 Example e = () => Console.WriteLine("一個Lambda表示式"); e(); } }
從程式碼中可以看到,匿名方法被替換成了這樣一句
() => { Console.WriteLine("一個Lambda表示式"); }
在Lambda表示式中=>
之前的是方法的引數,=>
之後是方法體
引數和方法體的編寫規則
- 編譯器通常可以根據上下文推斷出Lambda表示式的型別,但是當無法推斷的時候則必須顯式指定每一個引數的型別
// 能夠推斷引數型別
(x) => { return x; }
// 不能推斷引數型別
(int x) => { return x;}
- 沒有引數,一個引數和多個引數時的寫法
// 沒有引數時小括號不能省略
() => { Console.WriteLine("一個Lambda表示式"); }
(x) => { return x; }
// 只有一個引數時可以省略小括號
x => { return x; }
// 多個引數時小括號不能省略
(x,y,z) => { return x+y+z; }
- 方法體只有一條語句的時候,可以省略大括號,return也可以省略;方法體有多條語句時大括號不能省略
x => { return x; }
// 一條語句可以省略大括號
x => return x;
// 一條語句可以省略return
x => x;
// 多條語句時不能省略大括號和return
x =>
{
Console.WriteLine("看看");
return x;
};
Lambda表示式的閉包和foreach
Lambda表示式可以引用方法內定義的區域性變數和方法的引數(外部變數)
Lambda表示式所引用的外部變數稱為捕獲變數,捕獲變數的表示式稱為閉包
using System;
class Program
{
static void Main(string[] args)
{
int x = 2;
Func<int, int> sum = n => n + x;
Console.WriteLine(sum(10)); // 輸出12
}
}
在這個例子中,x就是被捕獲的變數
捕獲變數的值
Lambda表示式捕獲的變數是在呼叫委託時賦值,而不是在捕獲時賦值
using System;
class Program
{
static void Main(string[] args)
{
int x = 2;
// 捕獲外部變數x,但此時並沒有賦值
Func<int, int> sum = n => n + x;
x = 10;
// 呼叫個委託時才賦值,此時x是10
Console.WriteLine(sum(10)); // 輸出20
}
}
捕獲變數的生命週期會延伸到和委託的生命週期一致
Lambda表示式foreach的兩個版本
如果Lambda捕獲迭代變數,最後會有怎樣的結果
using System;
class Program
{
static void Main(string[] args)
{
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
actions[i] = () => Console.WriteLine(i);
}
foreach (var a in actions)
{
a();
}
// 輸出333
}
}
先來看這個例子,利用Lambda表示式捕獲了for迴圈的i
變數,但此時i
的值並沒有確定,前面說過,Lambda捕獲的變數在呼叫時才賦值,所以這雖然捕獲了三次i
,但這三次都是捕獲的同一個i
,所以最後在呼叫時賦值了i
的最後的值3(前面說過,捕獲變數的生命週期會延伸到和委託的生命週期一致,雖然for迴圈結束了,但是因為Lambda的捕獲延長了生命週期,3這個值保留了下來),所以最後輸出的是333
如果要解決這個問題,只需要將迴圈變數指定到內部的變數中,即
using System;
class Program
{
static void Main(string[] args)
{
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
int temp = i;
actions[i] = () => Console.WriteLine(temp);
}
foreach (var a in actions)
{
a();
}
// 輸出012
}
}
對於lambda表示式來說,捕獲了三次temp,但是每一次都是新定義的temp,所以不受影響
下面來看一個foreach的“BUG"
using System;
class Program
{
static void Main(string[] args)
{
Action[] actions = new Action[3];
int i = 0;
foreach (char c in "abc")
{
actions[i++] = () => Console.WriteLine(c);
}
foreach (Action action in actions)
{
action();
}
// 輸出abc
}
}
這裡輸出結果是abc,這是因為foreach的每一個迭代變數都是不可變的,所以可以理解為循壞體中的區域性變數,也就是類似於上面的temp
,但是,在C#5.0之前,結果並不是這樣的,foreach會像前面的for語言一樣解析,如果遇到老版本的程式碼,一定要特別注意