[C# 3.0 入門] [第一章 Lambda表達式] 第四節:Lambda的用途 & 類型聲明能夠和不能夠省略的情況
成問題的是,雖然為了源代碼的簡潔性,很想用Lambda表達式,但是要寫的代碼卻不能全部都用Lambda表達式來寫。
那麼, Lambda表達式究竟能做到什麼程度呢?
習慣了C/C++編程風格的程序員,一定以為因C#語法與之很相似,所以用C#編寫相對復雜的程序應該也沒有問題。可是很遺憾,情況不是這樣。那是因為C/C++具有能寫出復雜功能的表達式的逗號表達式,而C#卻沒有。
例如,C/C++中,下面的代碼是可行的。
int a,b,c;
c = (a=1, b=2, a+b); // a=1、b=2、c=a + b
printf("%d\n",c);
正因為僅僅一個表達式就能做相當多的處理,所有逗號表達式才有著重要的意義。
但是,C#卻沒有這樣的用法。
但是C#有三元運算符?:和空接合運算符??。通過使用這些運算符,相當數量的代碼都可以用Lambda表達式來寫了。
例如,“根據參數指定的文件名向文件中寫入字符串,參數為null的情況下,用‘default.txt’作為默認文件名”這樣的Lambda表達式,像下面那樣,用空接合運算符??就可以用Lambda表達式來寫。
using System;
class Program
{
static void Main(string[] args)
{
Action<string> method =
(filename) => System.IO File.WriteAllText(
filename ?? "default.txt", "Hello!");
method(null); // 生成default.txt
method("hello.txt"); // 生成hello.txt
}
}
List11 使用了空接合運算符??的Lambda表達式
或者“參數的flag如果是false的話,文件名就是‘normal.log’,true的話就是‘system.log’”的情況下,使用三元運算符(?:)按照如下的方式以表達式形式的Lambda來寫。
using System;
class Program
{
static void Main(string[] args)
{
Action<bool> method =
(system) => System.IO.File.AppendAllText(
system ? "system.log" : "normal.log", "log message\r\n");
method(false); // 生成normal.log
method(true); // 生成system.log
}
}
List12 使用三元運算符(?:)的Lambda表達式
但是,前一章的List10的例子裡,其中的if語句中就不能改用三元運算符來替換。如果試圖替換的話就是以下情況:
using System;
class Program
{
static void Main(string[] args)
{
Action<string> method = (filename) =>
filename == null
? Console.WriteLine("Hello!")
: System.IO.File.WriteAllText(filename, "Hello!");
method(null);
method("hello.txt");
}
}
List13 List10中用三元運算符改寫後(產生編譯錯誤)
這個代碼,會產生以下的編譯錯誤:
error CS0201: 只有 assignment、call、increment、decrement 和 new 對象表達式可用作語句。
error CS0173: 無法確定條件表達式的類型,因為“void”和“void”之間沒有隱式轉換。
這並不是說Lambda表達式不能調用具有void返回值的方法。下面的代碼就沒有問題。
Action<string> method =
(filename) => System.IO.File.WriteAllText(filename, "Hello!");
這裡產生error的原因是,三元運算符的第二個、第三個運算數不能寫成void類型的表達式(因為這樣寫,void沒法隱式轉換成第二個、第三個運算數的類型,所以整個表達式的類型就無法判斷了。
因為存在這樣的問題,有void類型的返回值的表達式情況下,三元運算符使用就很困難。其實,void返回值的表達式很難理解,不能寫反而是個好事。
除了void型以外,其它類型的表達式,使用三元運算符就沒有問題。下面的代碼在編譯和執行時就沒有問題。
1using System;
2
3class Program
4{
5 static void Main(string[] args)
6 {
7 Func<int, bool> method = (year) =>
8 year < 1994 ? year % 4 == 0 : year % 4 == 2;
9
10 Console.WriteLine("冬奧會年份");
11 for (int i = 1988; i < 1999; i++)
12 {
13 Console.WriteLine("{0}年={1}", i, method(i));
14 }
15 // 輸出:
16 // 冬奧會年份
17 // 1988年=True
18 // 1989年=False
19 // 1990年=False
20 // 1991年=False
21 // 1992年=True
22 // 1993年=False
23 // 1994年=True
24 // 1995年=False
25 // 1996年=False
26 // 1997年=False
27 // 1998年=True
28 }
29}
30
List14 使用三元運算符的Lambda表達式
代入變量method的Lambda表達式,判斷參數的年份是不是冬奧會舉行的年份,1994年以後,因為是兩年舉行一屆,所以判斷方式也要改變。這種情況下,當然是三元運算符派上用場的時候。總之,這種程度的問題,用表達式形式的Lambda就比較容易寫。
不用類型聲明的情況和必須類型聲明的情況
大部分情況下,Lambda表達式的參數類型都可以省略。然而,Lambda表達式的使用也存在限制,不能推定出類型的情況下就不能用Lambda表達式。
例如,Lambda表達式就不能代入到使用var關鍵字隱式類型聲明的局部變量:
var Lambda = (int x) =>x *2;
上面的代碼會產生“Lambda表達式局部變量隱式的類型聲明不會起作用”的錯誤。避免錯誤的方法是不要用var,而是明確的進行類型聲明。
然而,下面的例子
1using System;
2
3delegate int delegate1( int x );
4delegate int delegate2( string s );
5
6class Program
7{
8 private static void sample(delegate1 method)
9 {
10 Console.WriteLine("void Sample(delegate1 method)");
11 }
12
13 private static void sample(delegate2 method)
14 {
15 Console.WriteLine("void Sample(delegate2 method)");
16 }
17
18 static void Main(string[] args)
19 {
20 sample((int x) => 0);
21 // sample((x) => 0); // 如果沒有參數類型聲明會產生錯誤
22 }
23}
24
List 15 必須指定參數類型的情況
這個例子中,“sample((x)=> 0”會產生編譯錯誤。滿足條件的sample方法有兩個,所有就不能確定究竟應該使用哪個。然而,在參數前加上類型聲明“sample((int x) => 0”,就能夠編譯執行。因為參數類型的指定,在2個sample方法中,有一個與之類型相吻合,所以以此為依據就能夠選擇了。
什麼都不做的Lambda表達式
這個話題說到此,還有盲點。這裡先說明一下什麼都不做的Lambda表達式的寫法。
Lambda表達式沒有返回值的情況(void的情況),想使其內容為空的情況下(調用後什麼也不執行的Lambda表達式選擇使用的情況),可以使用內容為空的Lambda語句。
例如,下面這個的Lambda表達式:
(x) => { };
這樣用Lambda表達式重構,解決了“引入了null值對象”的問題。一句話,不應該用null表示什麼也不做的表達式,而是采用調空Lambda表達式的手法。
簡單的說,分別用代碼來展示能夠使用和不能夠使用這個技術的場合。
首先,說說不能夠使用該技術的場合。下面的代碼,因為什麼也不需要處理,所以用null值表示的例子。Sample方法的參數action,僅在值不為null的情況下被調用。
using System;
class Program
{
private static void Sample(Action<string> action)
{
if (action != null) action("Hello!");
}
static void Main(string[] args)
{
Action<string> action = null;
Sample(action);
action = (x) => Console.WriteLine(x);
Sample(action); // 輸出:Hello!
}
}
List 16 執行時沒什麼可處理的情況下用null表示的例子
相反,下面的代碼,在沒什麼要執行的情況下,要使用空Lambda表達式表示的情況。沒必要判定Sample方法的參數action是否為null。如果需要處理的內容不存在的情況下,僅僅用空的Lambda表達式來執行,什麼也不做就返回。
using System;
class Program
{
private static void Sample(Action<string> action)
{
action("Hello!");
}
static void Main(string[] args)
{
Action<string> action = (x) => { };
Sample(action);
action = (x) => Console.WriteLine(x);
Sample(action); // 輸出:Hello!
}
}
List 17 沒什麼可處理的情況下用空表達式的例子
這樣的“什麼也不做的Lambda表達式(或是以前的匿名方法)”,是筆者經常使用的技術。
例如,現在正在寫的程序,具有用戶的操作用報表的形式回放的功能,通過該功能進行自動測試。這個時候,回放中與輸出有關的處理會全部禁用,以提高其運行效率。這些操作的實現,並不需要具有輸出功能的方法對條件進行一個一個的判斷,只要用“空Lambda表達式(匿名方法)”就行了。因此,源代碼仍然能夠維持其簡潔,成功實現了隨時都能夠執行的自動測試效率的目的。