C# 8.0 搶先看 -- Recursive patterns

Recursive patterns 是一個非常強大的語法糖,個人覺得真的把『懶還要更懶』發揮到極致。
玩轉 WPF 課程招生中,傳送門https://skilltree.my/events/9cbgp

本篇文章使用環境
開發環境 Visual Studio 2019 Preview 4 (16.0.0 Preview 4)
框架       .NET Core 3.0.100-preview-010184
編譯器    C# 8.0 beta

C# 7.0 新增了 pattern matching 的功能,降低程式碼中需要轉型與比對的困擾,本來我以為這已經很猛了,但更猛的還在後頭。C# 8.0 為 pattern matching 衍生了許多的變化形,其中一項就是 recursive patterns,這玩意用在 Composite object 時特別會讓人有一種愉悅感。

以下拿個簡單的範例展示一下 recurisve patterns :

首先來設定簡單的資料模型:

   interface IPerson
    {
        Guid ID { get; set; }
        string Name { get; set; }
    }

    class Teacher : IPerson
    {
        public Guid ID { get; set; }
        public string Name { get; set; }
        public string Subject { get; set; }
    }

    class Student : IPerson
    {
        public Guid ID { get; set; }
        public string Name { get; set; }

        public bool IsPassed
        {
            get { return Score > 59; }
        }

        public Gender Gender { get; set; }

        public int Score { get; set; }
    }

    public enum Gender
    {
        Male, Female
    }


有兩個類別分別實作了 IPerson -- Teacher class 與 Student class。接著寫個製作測試用資料的方法:

  static List<IPerson> Create()
  {
      return new List<IPerson>
      {
          new Teacher {Name= "Bill" },
          new Teacher {Name= "David"},
          new Student{ Name = "魯夫",  Gender = Gender.Male , Score= 60},
          new Student{ Name = "妮可羅賓",  Gender = Gender.Female , Score= 82},
          new Student{ Name = "娜美",   Gender = Gender.Female, Score= 70 },
          new Student{ Name = "騙人布" ,Gender = Gender.Male, Score= 55 },
          new Student{ Name = "香吉士",  Gender = Gender.Male, Score= 58 },
          new Student{ Name = "喬巴",   Gender = Gender.Male, Score= 67 },
          new Student{ Name = "布魯克",  Gender = Gender.Male, Score= 42 },
          new Student{ Name = "索隆",  Gender = Gender.Male, Score= 80 },
          new Student{ Gender = Gender.Male , Score= 60},
          new Student{ Name = string.Empty,  Gender = Gender.Male , Score= 99},
      };
  }


準備工作都完成了,現在來假設一個情境,如果需求是從這個 List 中取得以下條件的資料 (1) 學生 (2) 男性 (3) Name 不可為 null (4) 有通過測驗,亦即 IsPassed 為 true。在 C# 7.0 時可能會這樣寫 (這邊我用上了 VauleTuple 作為回傳的型別):

 static IEnumerable<(string Name, int Score)> GetStudentIsPassedAndMale()
 {
     var people = Create();

     return people.Where((x) => x is Student student 
                         && student.IsPassed == true 
                         && student.Gender == Gender.Male 
                         && student.Name != null).Select((y) =>
                                                  {
                                                      Student s = y as Student;
                                                      return (s.Name, s.Score);
                                                  });
 }

其實個人覺得這寫法已經很簡單了,至少在型別比對轉換少了些工要做。但如果是使用  C# 8.0 的 recursive patterns,更是簡單到令人難以置信 。以下為使用 recursive patterns 的程式碼:

 static IEnumerable <(string Name, int Score)> GetStudentIsPassedAndMale()
 {    
     var people = Create();
     foreach (var p in people)
     {
         if (p is Student { Gender: Gender.Male, IsPassed: true, Name: string name, Score: int score })
         {
             yield return (name, score);
         }
     }         
 }

說明如下:

1. p is Student 代表只有選擇 Student 類別的執行個體。

2. Gender: Gender.Male, IsPassed: true 表示選擇的條件為 Gender 屬性為 Gender.Male 且 IsPassed 屬性為 true 者。

3. Name: string name 表示如果 Name 屬性不為 null 者 (如果是 null 會被排除於結果外),將其 Name 屬性的值設定給 string 型別的 name 變數。

4. Score: int score 表示將其 Score 屬性值設定給 int 型別的 score 變數。

5. 最後 yield return ValueTuple<string,int>

 寫起來超直覺,我覺得這功能真是超嗨的。

範例位於:Recursive patterns samples