.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N170818601
出刊日期: 2017/8/9
本文將延續本站《C#7新功能概覽 - 1》一文的說明,介紹C# 7 新增的新語法,並利用一些範例來了解這些語法。
Pattern matching
C# 7的Pattern matching可以根據特定的類別或是結構來進行比對,看看比對的結果是否符合特定的樣式。Pattern matching功能支援兩個運算式(Expression):is與switch。這些運算式可以檢視物件與物件的屬性,來判斷物件是否滿足某種樣式(Pattern),還可以搭配when關鍵字以指定樣式的相關規則。
is樣式運算式(Pattern Expression)
is樣式運算式(Pattern Expression)擴充原有的is運算子(Operator)的功能,以根據型別來查詢物件。
例如以下範例程式碼,在集合中存放數值與包含NT$貨幣符號的字串,我們希望將計算出所有項目的加總,若集合中存放的是數值,直接累加到total,若集合中存放的是帶貨幣符號的字串,則先將字串根據台灣文特性來轉型,粹取出數值,再和total進行累加,在C# 6可以撰寫這樣的程式碼來達到這個需求:
class Program
{
static void Main(string[] args)
{
int x = 100;
string y = "NT$200";
IEnumerable<object> data = new List<object>() { x, y };
int total = 0;
foreach (var item in data)
{
if (item is int)
{
total += (int)item;
}
else if (item is string && item.ToString().Contains("NT$"))
{
total += int.Parse(item.ToString(), NumberStyles.Currency, new CultureInfo("zh-TW"));
}
}
Console.WriteLine(total);
}
}
此範例的執行結果會印出累計的結果「300」,而在C# 7可以改寫如下:
class PatternMatchingDemo
{
static void Main(string[] args)
{
int x = 100;
string y = "NT$200";
IEnumerable<object> data = new List<object>() { x, y };
int total = 0;
foreach (var item in data)
{
if (item is int i)
{
total += i;
}
else if (item is string s && s.Contains("NT$"))
{
total += int.Parse(item.ToString(), NumberStyles.Currency, new CultureInfo("zh-TW"));
}
}
Console.WriteLine(total);
}
}
比對程式碼,在C# 7 if判斷式中,使用is比對集合中的項目的型別若為「int」,可以直接將item指定一個變數名稱「i」,在if的區塊中使用它。而第二個if 則利用一行程式碼,完成兩個動作,首先在判斷集合中的項目是否是「string」型別,若是字串型別,則自動將其轉型成字串,並指定給s變數。因此不需要像C# 6 還需要明確叫用ToString()方法將物件轉型成字串。
若集合中項目存放的是「null」時,要將total做累加一動作,例如以下將z變數放到集合中,此時就可以利用is運算子進行判斷:
class PatternMatchingDemo
{
static void Main(string[] args)
{
int x = 100;
string y = "NT$200";
int? z = null;
IEnumerable<object> data = new List<object>() { x, y, z };
int total = 0;
foreach (var item in data)
{
if (item is null)
{
total += 1;
}
else if (item is int i)
{
total += i;
}
else if (item is string s && s.Contains("NT$"))
{
total += int.Parse(item.ToString(), NumberStyles.Currency, new CultureInfo("zh-TW"));
}
}
Console.WriteLine(total);
}
}
switch式運算式(Pattern Expression)
若要判斷的條件有很多,不同的條件要分支去執行不同的程式碼,使用switch語法會比if語法看起來更為簡潔。在新的C# 7 switch語法,有著顯著的改善,可以撰寫類似VB的Select Case語法,更容易判斷資料是否在一個範圍,或符合特定條件。
以往在C# 6時,當運算分支的邏輯不是只拿一個簡單的型別做比對時,只能使用if - else if語法,現在C# 7使用新的Pattern matching語法可以讓程式更容易達到這樣的要求。switch陳述式與 case關鍵字之後的運算式現在不限定在只能使用數值(如int),或字串型別,可以包含變數或運算式,也可以使用到.NET型別或自訂類別。若在case關鍵字之後使用到「null」,則「null」將視為一個常數運算式(constant expresion),可以匹配null參考型別物件(null reference-type object)。
參考以下範例程式碼,從主控台輸入一個課程的成積,並透過ParseNullable方法,將其轉型成數值印出:
class Program
{
public static int? ParseNullable(string val)
{
int output;
return int.TryParse(val, out output) ? (int?)output : null;
}
static void Main(string[] args)
{
Console.WriteLine("請輸入一個數字");
var scores = ParseNullable(Console.ReadLine());
Console.WriteLine("中文");
switch (scores)
{
case 100:
Console.WriteLine("滿分");
break;
case var s when (s == 0):
Console.WriteLine("零分");
break;
case var s when (s >= 99 & s <= 60):
Console.WriteLine("及格");
break;
case var s when (s < 60 & s > 0):
Console.WriteLine("不及格");
break;
default:
Console.WriteLine("不正確的成績");
break;
}
}
}
在第一個case區塊中,將數值與常數「100」做比對,而第二個case則在後面的運算式中,使用when關鍵字加上判斷條件,在成績(scores)為「0」時印出「零分」。第三個case則判斷成績是否在 99~60分之間;最後一個是預設值,因此若輸入負數和英文文字,如「a」,則會印出「不正確的成績」。
若輸入負數和英文文字要執行不同的程式邏輯,我們可以在switch的case區段判斷null值,
參考以下範例程式碼:
class Program
{
public static int? ParseNullable(string val)
{
int output;
return int.TryParse(val, out output) ? (int?)output : null;
}
static void Main(string[] args)
{
Console.WriteLine("請輸入一個數字");
var scores = ParseNullable(Console.ReadLine());
Console.WriteLine("中文");
switch (scores)
{
case 100:
Console.WriteLine("滿分");
break;
case var s when (s == 0):
Console.WriteLine("零分");
break;
case var s when (s >= 99 & s <= 60):
Console.WriteLine("及格");
break;
case var s when (s < 60 & s > 0):
Console.WriteLine("不及格");
break;
case var s when s is null:
Console.WriteLine("null");
break;
default:
Console.WriteLine("不正確的成績");
break;
}
}
}
此範例若輸入負數印出「不正確的成績」,若輸入英文文字,根據ParseNullable方法的邏輯,若轉型失敗ParseNullable方法會回傳null,因此switch程式區段最後會印出「null」。
特別注意,在switch使用Pattern Expression時,case出現的順序會影響程式的執行,規則比較嚴格或特殊的case區塊,程式的位置要出現在比較上面。我們將前面範例改簡單一些,參考以下範例程式碼:
class Program
{
public static int? ParseNullable(string val)
{
return int.TryParse(val, out int output) ? (int?)output : null;
}
static void Main(string[] args)
{
Console.WriteLine("請輸入一個數字");
var scores = ParseNullable(Console.ReadLine());
Console.WriteLine("中文");
switch (scores)
{
case 100:
Console.WriteLine("滿分");
break;
case var s when (s > 0):
Console.WriteLine(s);
break;
default:
Console.WriteLine("不正確的成績");
break;
}
}
}
若輸入100則印出「滿分」;若輸入非100的正數值,就印出此數值,但若修改程式碼順序如下:
class Program
{
public static int? ParseNullable(string val)
{
return int.TryParse(val, out int output) ? (int?)output : null;
}
static void Main(string[] args)
{
Console.WriteLine("請輸入一個數字");
var scores = ParseNullable(Console.ReadLine());
Console.WriteLine("中文");
switch (scores)
{
case var s when (s > 0):
Console.WriteLine(s);
break;
case 100:
Console.WriteLine("滿分");
break;
default:
Console.WriteLine("不正確的成績");
break;
}
}
}
因為第二個case涵蓋在第一個case的範圍內,因此Visual Studio會顯示一個錯誤,無法編譯程式碼:
圖 1:switch case出現的順序會影響程式的編譯。
比對型別(Type )
在每一個「case」後的運算式(Expression)可以包含一個型別(Type)名稱再加一個變數名稱,根據型別來比對是否滿足條件,此變數的有效範圍限定在此case區塊之中。Null實體永遠不會匹配這些型別判斷的case區塊,若要判斷是否為Null值,直接使用一個「null」 實體即可,參考以下範例程式碼:
class PatternMatchingDemo
{
static void Main(string[] args)
{
Employee employee1 = new Employee() { Name = "Mary" };
Employee employee2 = new Sales() { Name = "Candy", Bonus = 1000 };
Employee employee3 = new SalesLeader() { Name = "LuLu", Allowance = 5000 };
var list = new List<Employee>() { employee1, employee2, employee3, null };
foreach (var employee in list)
{
switch (employee)
{
case SalesLeader l:
Console.WriteLine($"Employee Name {l.Name} , SalesLeader Allowance : {l.Allowance}");
break;
case Sales s:
Console.WriteLine($"Employee Name {s.Name} , Sales Bonus : {s.Bonus}");
break;
case null:
Console.WriteLine($"null");
break;
default:
Console.WriteLine($"Employee Name {employee.Name}");
break;
}
}
}
}
class Employee
{
public string Name { get; set; }
}
class Sales : Employee
{
public int Bonus { get; set; }
}
class SalesLeader : Sales
{
public int Allowance { get; set; }
}
SalesLeader類別繼承自Sales類別,而Sales類別繼承自Employee類別,範例中將SalesLeader、Sales與Employee物件放到集合之中,使用迴圈將集合中每一個物件取出,然後利用switch比對,若集合中的物件是SalesLeader類型的物件則印出Allowance;若集合中的物件是Sales類型則印出Bonus,此範例的執行結果,請參考下圖所示:
圖 2:型別比對。
進行型別比對(Type)時,case關鍵字後的運算式也可以搭配使用when關鍵字,例如將上例改寫如下,可以得到相同的執行結果:
class PatternMatchingDemo
{
static void Main(string[] args)
{
Employee employee1 = new Employee() { Name = "Mary" };
Employee employee2 = new Sales() { Name = "Candy", Bonus = 1000 };
Employee employee3 = new SalesLeader() { Name = "LuLu", Allowance = 5000 };
var list = new List<Employee>() { employee1, employee2, employee3, null };
foreach (var employee in list)
{
switch (employee)
{
case SalesLeader l:
Console.WriteLine($"Employee Name {l.Name} , SalesLeader Allowance : {l.Allowance}");
break;
case Sales s:
Console.WriteLine($"Employee Name {s.Name} , Sales Bonus : {s.Bonus}");
break;
case null:
Console.WriteLine($"null");
break;
default:
Console.WriteLine($"Employee Name {employee.Name}");
break;
}
}
}
}
class Employee
{
public string Name { get; set; }
}
class Sales : Employee
{
public int Bonus { get; set; }
}
class SalesLeader : Sales
{
public int Allowance { get; set; }
}