繼續 record 的其他話題
Visual Studio 2019 - 16.8.0 Preview 3.1
.NET 5.0.0-rc.1.20451.14
C# 9.0 為 record 帶來了一個瘋狂的使用方式 (我第一次見到類似的用法是在 Kotlin 見到,真是個很炫的用法) -- positional records。這個功能就是一種極簡化的宣告,我們可以用以下的方式宣告一個 record:
record Person(string Name, int Age);
你沒看錯,就是這麼一行。除前一篇提到編譯器自動生成的部分 (不包括建構式),同時會為 Name 和 Age 生成兩個屬性 (當然也包含了背後的欄位),這些自動生成屬性的存取子則會被設定為 {get; init;} ;而且還會補上一個 Deconstruct 方便搭配 ValueTuple 使用。
另外在建構式的部分,編譯器自動產生兩個建構式 ( 不會生成無參數建構式)
(1) protected Person (Person original)
(2) public Person(string Name,int Age)
所以上面的程式碼和以下的寫法是同義的:
record Person
{
public string Name { get; init; }
public int Age { get; init; }
protected Person(Person original)
{
this.Name = original.Name;
this.Age = original.Age;
}
public Person(string Name, int Age)
{
this.Name = Name;
this.Age = Age;
}
public void Deconstruct(out string Name, out int Age)
{
Name = this.Name;
Age = this.Age;
}
}
這個建構式使得 positional record 無法使用 new Person() { Name = "Bill", Age =28 } ; 這樣的語法。
註:這裡的 positional 指的是 positional creation,簡單來說就是利用建構式的參數順序傳遞各欄位初始值來初始化執行個體的一種程式設計形式。
record 的繼承有一些限制,即使 record 根本上是個 class,但是使用 record 宣告時它只能繼承 object 或其他 record;在此同時 record 也只能成為 record 的基底類別,我們無法宣告一個繼承自某個 record 的 class。
假設我們現在有一個 Student positional record 會繼承自前面的 Person positional record ,可以寫成以下的形式:
record Student(string Name, int Age, string Id): Person (Name, Age);
這表示 Student record 擴張了一個屬性 Id,由於 Person record 並不具備無參數建構式,所以這個寫法同時也表示 Student 的建構式要呼叫其基底類別 Person 的 public Person(string Name,int Age) 建構式。
編譯器同時也會 override 其基底型別的 Equals -- 以 Person 來講有兩個,因為 Person 本身也 override 了 object 型別的 Equals 方法,加上自己本身實作 IEquatable<Student>,所以會有三個 Equals:
public override bool Equals(object obj)
public sealed override bool Equals(Person other)
public virtual bool Equals(Student other)
比較有趣的事情是以 Person 型別為參數的 Equals 被標示 sealed,這表示若還有另外一個 record 繼承了 Student record,一樣只會出現三個 Equals;參數分別為 object 、 Student 和它自己。
滿有趣的新玩意,有機會你應該試試看。