[C# 8.0] 模式比對強化 Pattern matching enhancements

熱門文章 (Popular Post)

Posted by : Duran Hsieh 2020-02-06

前言

在 C# 7.0 新功能引進了基本的模式比對 (pattern matching) 功能,包含了 is 模式switch 內使用 when 模式,與 解構 (desconstruction) 而在 C# 8.0 新功能,對於這些功能進一步強化,其中包含:

1. Switch 運算式 (Switch Expressions)
2. 屬性模式 (Property Pattern)
3. Tuple 模式 (Tuple Pattern)
4. 位置模式 (Positional Pattern)

本篇文章簡單介紹這 4 種 pattern,若有任何錯誤或任何建議,請各位先進不吝提出,謝謝。



介紹

Switch 運算式 (Switch Expressions)

過去 switch 敘述句必須透過 case 區塊回傳結果,如下列程式碼所示:
public static string TranslateStatus(Status playerStatus)
{
    switch (playerStatus)
    {
        case Status.Actived:
            return "啟用";
        case Status.Inactived:
            return "不啟用";
        case Status.Archived:
            return "封存";
        case Status.Maintained:
            return "維護";
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(Status));
    };
}

其中, Status 是一個列舉資料格式:
public enum Status
{
    Actived,
    Inactived,
    Archived,
    Maintained
}

現在透過 switch expression,您可以使用較精簡的方式撰寫 switch (減少大量的 大括號、case 關鍵字 與 break 關鍵字),以下列程式為例:
public static string TranslateStatus(Status playerStatus) =>
    playerStatus switch
    {
        Status.Actived    => "啟用",
        Status.Inactived  => "不啟用",
        Status.Archived   => "封存",
        Status.Maintained => "維護",
        _                 => throw new ArgumentException(message: "invalid enum value", paramName: nameof(Status)),
    };
透過上列程式碼,您可以發現 switch 運算式有下列特色
    1. 變數 (playerStatus) 移到 switch 關鍵字前
    2. case 及 : 元素取代為 =>
    3. default 取代為 _ 捨棄
    4. 主體為運算式,而非陳述式
整體來說,語法呈現更加簡潔且直覺,若您使用 .NET Core 3.0 (或以上) 進行開發,不妨可以試試看 switch 運算式。



屬性模式 (Property Pattern)

顧名思義,在進行比對時您可以透過物件內的屬性進行比對。如下範例,我們有個 Address 物件,內部其中一個屬性是 City,我們能直接比對這個屬性:
public static string CityCode(Address location) =>
    location switch
    {
        { City: "Taipei" }    => "1",
        { City: "Taichung" }  => "4",
        { City: "Kaohsiung" } => "8",
        _ => ""
    };
屬性模式可以讓您不用撰寫重複的物件名稱,直接取用內部屬性進行運算。透過此模式,上列程式碼看起來更加簡單且直覺。



Tuple 模式 (Tuple Pattern)

理所當然,若沒有建立類別 (class),但仍需要多個參數,這個時候您能透過 Tuple Pattern 方式進行比對,如下範例所示:
public static string LogicalConjunction(string first, string second)
    => (first, second) switch
    {
        ("T", "T") => "T",
        ("T", "F") => "F",
        ("F", "T") => "F",
        ("F", "F") => "F",
        (_, _) => "invalid value"
    };
這是一個 AND 的真值表,當輸入兩者為 T,則回傳 T;若為 T 與 F,則回傳 F;若兩者為 F,則回傳為 F。



位置模式 (Positional Pattern)

在 C# 7.0 您可以使用透過解構 (Deconstruct) 方式將本身的屬性指派為離散的變數。如下列程式為例,解構式內 out 參數即為解構後的變數。您可以在 23 行看見解構後 tuple。理所當然,可以指定不同數量的解構式,讓你的程式更有彈性。
public class Leader
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Leader(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}

public static class CustomDeconstructorSample
{
    public static string GetLeaderInfo(Leader l)
    {
        (string x, string y) = l;
        return x + " " + y;
    }
}

解構的時候不需要帶參數 ?
依據 out 語法特性,可以忽略你不需要的參數  

C# 7.0 解構指派方式並沒有任何模式 (pattern) 可以使用,純粹回傳 tuple 取得其項目。在 C# 8.0 ,若具有 tuple 或 解構式,即可使用位置模式 (不需要命名屬性) ,應用遞迴模式之嚴謹方式進行比對。
若物件 (object) 比對確定,則會透過解構式以巢狀模式 (nested pattern) 應用在結果值上。


以下面程式為例,我們宣告了兩個類別,分別是 Member 與 Leader,皆有 FirstName 與 Last Name 兩個屬性。另外, Member 多了一個屬性: Leader ...
public class Member
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Leader Manager { get; set; }

    public Member(string firstName, string lastName, Leader manager)
    {
        FirstName = firstName;
        LastName = lastName;
        Manager = manager;
    }

    public void Deconstruct(out string firstName,
        out string lastName,
        out Leader manager)
    {
        firstName = FirstName;
        lastName = LastName;
        manager = Manager;
    }
}

public class Leader
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Leader(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}

public static class PositionalPatternSample
{
    public static bool IsMember(Member m)
    {
        return m is Member(_, _, Leader(_, _));
    }
}

在 PositionalPatternSample 內的 IsMember 方法,可以看見我們對於內物件進行比對。你可以將 第 ? 行 Member 與 Leader 兩個類別名稱移除,透過位置模式直接進行比對 (如下程式)。
public static class PositionalPatternSample
{
    public static bool IsMember(Member m)
    {
        return m is (_, _, (_, _));
    }
}

這是一個相當簡單的範例,雖然使用 if 敘述句有更好可讀性與可維護性,但使用 if 敘述句就沒有辦法達成遞迴比對的效果。

參考資料

2. C# 8.0 的新功能 - Microsoft Docs
3. 解構元組和其他類型 - Microsoft Docs


Leave a Reply

Subscribe to Posts | Subscribe to Comments

- Copyright © Duran Hsieh @ Duran 的技術冶煉廠 - Date A Live - Powered by Blogger - Designed by Johanes Djogan -