本次的內容,主要是針對已經學習過C# 2.0的程序員讀者的,前提是已經知道什麼是匿名方法。如果還不清楚,請先閱讀“連載:C# 2.0入門”(這篇會在今後翻譯)。
好的,現在進入正題。
Lambda表達式(λ表達式),用一句話來解釋,(不算很嚴謹)就是使匿名方法文字上更短的語法。雖然這樣說,單“僅僅是文字上的變化,源代碼的性質沒有變”這樣的想法也是一種誤解。規模變化了,其性質也會變化。例如,實驗室的燒杯中產生的現象,不一定會在大型的工廠裡產生。同樣的道理也適用於源代碼。
那麼,就體驗一下Lambda表達式吧。
以下,使用具體的code來說明一下,不是實際工程中的代碼,而是實際代碼使用C# 2.0重寫的。
前一陣子筆者實際寫的code中,有一個菜單,能夠選擇的下拉菜單。菜單項是下面這樣定義的:
1public delegate bool SimpleMenuAction();
2
3public class MenuItemA // 菜單項
4{
5 public readonly string Name; // 名字
6 public readonly SimpleMenuAction Action; //執行內容
7
8 public MenuItemA(string name, SimpleMenuAction action)
9 {
10 Name = name;
11 Action = action;
12 }
13}
14
List 1 菜單項的定義
與之相對應,以下是菜單項的數組。
1private static MenuItemA[] Menu Items1 =
2 {
3 new MenuItemA("選擇項1", 執行方法),
4 new MenuItemA("選擇項2", 執行方法),
5 new MenuItemA("選擇項3", 執行方法),
6 };
7
List 2 菜單項目數組
實際上,當時認為這樣就足夠了,誰知中途又被要求加入一種菜單項,這種菜單項在19點以後才可以看到。如果只有這一個的話,用if語句括起來判斷一下例外條件就能夠處理,可是要求是2個,而且還可能增加。於是,就想在這個表中添加條件語句。
最simple的解決方案,應該就是在MenuItem類裡,保存“幾點以後有效”的“幾點”的整數值。
首先在MenuItemA類中,加上保存時間的整數字段“FromHour”。
1public class MenuItemB
2{
3 public readonly string Name;
4 public readonly SimpleMenuAction Action;
5 public readonly int FromHour;
6
7 public MenuItemB(string name, SimpleMenuAction action, int fromHour)
8 {
9 Name = name;
10 Action = action;
11 FromHour = fromHour;
12 }
13}
14
List 3 加上時間字段的菜單項定義
菜單項數組也改寫成以下:
1private static MenuItemB[] MenuItems2 =
2 {
3 new MenuItemB("選擇項1", 執行方法, 0),
4 new MenuItemB("選擇項2", 執行方法, 0),
5 new MenuItemB("選擇項3", 執行方法, 0),
6 new MenuItemB("選擇項4", 執行方法, 19),
7 };
8
List 4 對List 3的菜單項進行修正
這樣,需要的信息都能夠包含在數組裡了。
構建菜單的方法是,通過檢查被選擇的菜單對象的FromHour的值,如果與現在時間相比小,就把該菜單項顯示出來。
這麼看來,這個code如果按照YAGNI*的原則來看的話就比較完善了,這樣的代碼也屬於良品了。
* YAGNI是“You Aren't Going to Need It.”的簡寫,意思是:或許是必要的功能實際上並不必要的可能性非常高。一句話,為未知的未來而事先准備的代碼,基本上是沒用的。這樣的教訓很多。
但是,這個代碼來應對需求變更的要求,恐怕還太脆弱。例如,條件如果從19點改為19點半,就沒法辦了。或者要求設置個結束時間,或者是個時間段的話,或是根據星期幾而變動,這種要求實在是太多了。
琢磨了一下要求,加入能夠指定條件的代碼,修正後如下:
1public delegate bool SimpleMenuAvailability();
2
3public class MenuItemC
4{
5 public readonly string Name;
6 public readonly SimpleMenuAction Action;
7 // 判斷現在是否是有效菜單項
8 public readonly SimpleMenuAvailability IsAvailable;
9
10 public MenuItemC(string name, SimpleMenuAction action, SimpleMenuAvailability isAvailable)
11 {
12 Name = name;
13 Action = action;
14 IsAvailable = isAvailable;
15 }
16}
17
List5 加上條件的菜單定義
如果使用匿名方法,數組就要改寫成如下:
Menu
1private static MenuItemC[] MenuItems3 =
2 {
3 new MenuItemC(
4 "選擇1", 執行方法, delegate() { return true; }),
5 new MenuItemC(
6 "選擇2", 執行方法, delegate() { return true; }),
7 new MenuItemC(
8 "選擇3", 執行方法, delegate() { return true; }),
9 new MenuItemC(
10 "選擇4", 執行方法, delegate() { return DateTime.Now.Hour >= 19; } ),
11 };
12
List 6 List 5中對應的菜單項
可是,這樣的代碼如果要為將來可能需要也可能不需要的變化准備的話,將會變得相當臃腫。本質上完全沒有意義的delegate和return顯得非常刺眼,看上去很難理解意圖。這種代碼就是YAGNI原則所說的那種應該避免的代碼的典型例子。
因此,如果使用C# 2.0,這種code應該就不會被采用。筆者雖然屬於那種對匿名方法使用得揮金如土的類型,在這個case上,(用匿名方法)恐怕優勢要小於劣勢。
然而,下面這並不是匿名方法,打眼一看就是較少的文字就能描述的Lambda表達式。下面的代碼,“()=>true”和“()=>DateTime.Now.Hour > 19”,一看就是Lambda表達式的樣子。
1private static MenuItemC[] MenuItems4 =
2 {
3 new MenuItemC("選擇項1", 執行方法, ()=>true),
4 new MenuItemC("選擇項2", 執行方法, ()=>true),
5 new MenuItemC("選擇項3", 執行方法, ()=>true),
6 new MenuItemC("選擇項4", 執行方法, ()=>DateTime.Now.Hour >= 19),
7 };
8
List 7 對List 6改用Lambda表達式後
說實話,code寫成這樣應該可以通過了。雖然違反了YAGNI的原則,但在不損害code的可理解性的范圍內,對於未知修改來說也是上了保險了。
事實上這個保險也確實起作用。很快,菜單的有效時間從“19點以後”變成“19點後22點前”。來吧,數組做如下修正:
1private static MenuItemC[] MenuItems5 =
2 {
3 new MenuItemC("選擇項1", 執行方法, ()=>true),
4 new MenuItemC("選擇項2", 執行方法, ()=>true),
5 new MenuItemC("選擇項3", 執行方法, ()=>true),
6 new MenuItemC("選擇項4", 執行方法,
7 ()=>DateTime.Now.Hour >= 19 && DateTime.Now.Hour < 22),
8 };
9
List 8 List 7中第四個菜單項的修改
這個變更,只用改寫一個Lambda表達式這樣的局部變更就能搞定,一瞬間的事。但是,如果采用最初的List 4的code,菜單項類還要加上結束時間,菜單的構建方法中還要加上判斷,相當的費事。然而,不僅如此,如果不是Lambda表達式,而是使用匿名方法的前提下,或許這種費事的方法也會得到采用。總之,匿名方法與Lambda表達式的長度上的差別對code會有質的影響。