C# 7 的新語法:模式匹配
文章目錄
這篇文章介紹的是 C# 7 新增的 pattern matching 語法。
C# 7 增加了「模式匹配」(pattern matching)的語法,可用來判斷某個變數或陳述式的「長相」是否符合特定條件,以決定程式要走哪一條執行路徑——這樣說也許太抽象了,待會兒看到範例程式會比較清楚了。
C# 7 在模式匹配這個部分,目前僅支援
C# 7 增加了「模式匹配」(pattern matching)的語法,可用來判斷某個變數或陳述式的「長相」是否符合特定條件,以決定程式要走哪一條執行路徑——這樣說也許太抽象了,待會兒看到範例程式會比較清楚了。
C# 7 在模式匹配這個部分,目前僅支援
is
和 switch
陳述式。從微軟的技術文件來看,未來應該會在這方面繼續強化。接著就來看看這個新語法有何特別之處。is
陳述式
在 C# 7 之前,如果要判斷某變數是否是某種型別,常會使用
is
運算子:void Print(object obj)
{
if (obj == null) // 若物件為空,便直接返回。
return;
if (obj is string) // 若物件是個字串,就輸出字串長度。
{
var s = obj as string;
Console.WriteLine($"{s.Length}");
}
}
C# 7 可以這樣寫:
void Print(object obj)
{
if (obj is null) // C# 7
return;
if (obj is string s) // C# 7
{
Console.WriteLine($"{s.Length}");
}
}
這裡有兩處改寫(註解中標示 C# 7 的地方):
- 第 3 行:判斷物件是否為
null
,現在也可以用is
了。儘管這算不上什麼大躍進,但的確提供了多一種選擇,讓我們可以一致地使用is
來判斷物件的「長相」。 - 第 5 行:這一行就完成了兩件事,即 (1) 判斷
obj
的型別是否為string
;(2) 將物件轉型並指派給新宣告的變數s
。
註:如果是判斷「不為
null
」的場合,我還是比較喜歡寫成 (obj != null)
,而不是 (!(obj is null))
。當然這只是個人喜好的問題。值得注意的是,以此方式宣告的區域變數,其有效存取範圍是「在外層包住它的那個區塊」。也就是說,底下的寫法完全沒有問題:
void Print(object obj)
{
if (!(obj is string s))
return;
Console.WriteLine(s); // 這裡仍可使用變數 s。
}
此範例的 .NET Fiddle 連結:https://dotnetfiddle.net/ywmqtI
switch
陳述式
上一節的範例程式碼使用了兩個
if
陳述式,而一旦要判斷的條件很多,使用 switch
會比較好。而且,switch
陳述式還可以使用 when
子句來進一步提供其他條件。範例如下:void Print(object obj)
{
switch (obj)
{
case null: // (1)
return;
case string s: // (2)
Console.WriteLine($"{s.Length}");
break;
case DateTime date: // (3)
Console.WriteLine(date.Year);
break;
case int[] numbers when numbers.Length > 0: // (4)
foreach (int n in numbers)
Console.WriteLine(n);
break;
case Box box when (box.Width > 0 && box.Width == box.Height)
// (5)
break;
default:
Console.WriteLine(obj.ToString());
break;
}
}
依註解中標示的編號說明:
- 跟
is
一樣,在switch
裡面也可以判斷變數是否為null
。 - 若
obj
是string
,就轉型成string
並指派至新宣告的變數s
。 - 若
obj
是DateTime
,就轉型成DateTime
並指派至新宣告的變數date
- 這裡使用了
when
子句:只有當obj
是個整數陣列,而且該陣列的長度大於零的時候,才會執行這個case
區塊裡面的程式碼。 - 只是再多一個
when
子句的例子,並沒有什麼特殊之處。
此範例的 .NET Fiddle 連結:https://dotnetfiddle.net/uRct75
現在讓我們來看一個稍微特別的情況。底下的程式碼在執行時會進入哪一個
case
區塊? string str = null;
switch (str)
{
case string s:
Console.WriteLine("字串");
break;
case null:
Console.WriteLine("null");
break;
}
執行結果是輸出字串 “null”,也就是執行了第二個
case
區塊。這是因為,儘管表面上來看,str
的型別是 string
,但是骨子裡它什麼東西都不是;它是個 null
。在判斷第一個 case
條件時,其作用等同於使用 as
轉型操作:if ((null as string) != null) // 結果為 false。
{
Console.WriteLine("字串");
}
由於
null
無法轉型為其他型別,因此 null as string
的轉型仍為 null
。於是,這個條件判斷式的結果為 false
。另外,底下是個錯誤示範:
switch (obj)
{
case string s1:
break;
case string s2 when s2.Length > 5: // 編譯失敗!
break;
}
這是因為第一個
case
的條件涵蓋了第二個 case
,亦即第二個 case
永遠不可能執行到。編譯器會幫你挑出這個有問題的寫法。