C# 9 record 並非簡單屬性 POCO 的語法糖
最近升級專案到大統一 .NET 5 並使用 C#9 語法嘗試改寫套件,發現之前以為 record 只是簡單屬性 POCO 的簡化語法糖的認知是錯誤。
另外因為 POCO 屬於需定義口語詞,這邊在本文定義簡單屬性 POCO
為 public class 類別 {public string ID{get;set}/*略*/}
只有屬性的簡單類別程式碼
一. rocord 的確底層是 class,但,不是單純簡單屬性 POCO class
可以看 IL Spy 反編譯程式碼,發現系統幫我們做了很多事
二. 預設
生成的是屬性是 {get;init;}
不是 {get;set;}
,這代表設定值
時間點在 constructor(建構式)
,延伸產生immutable(不可變)
特性,也代表 record 預設為thread-safe(執行緒安全)
,因為都是取得一樣的值。
所以當你使用 Dapper 類似框架查詢完 POCO 資料,想做修改屬性時會報 CS8852 無法修改錯誤。
三. 預設比較
邏輯改變
可以看TimCorey寫的例子,可以看到預設 class 跟 record 的 == 差異,線上測試連結
public class Program
{
public static void Main()
{
var record1Obj1 = new record1(FirstName: "Lin", LastName: "WeiHan");
var record1Obj2 = new record1(FirstName: "Lin", LastName: "WeiHan");
Console.WriteLine(record1Obj1 == record1Obj2);//true
var class1Obj1 = new Class1() { FirstName = "Lin", LastName = "WeiHan" };
var class2Obj2 = new Class1() { FirstName = "Lin", LastName = "WeiHan" };
Console.WriteLine(class1Obj1 == class2Obj2);//false
}
}
public record record1(string FirstName,string LastName);
public class Class1
{
public string FirstName {get;init;}
public string LastName{get;init;}
}
因為 record override ==
跟 Equals
,認為只要是同一個 record 型別,並且屬性值都一樣
,系統就會認定為true
,也就是俗稱的structural equality
,可以看 IL Spy 反編譯程式碼
public virtual bool Equals(record2? other)
{
return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(FirstName, other!.FirstName) && EqualityComparer<string>.Default.Equals(LastName, other!.LastName);
}
跟 object class 預設會去取得 RuntimeHelpers.GetHashCode
Handle 邏輯不相同。
四. GetHashCode也做了類似邏輯,所以屬性值一樣,HashCode會得到一樣的值
,線上測試連結
IL Spy 反編譯程式碼
public override int GetHashCode()
{
return (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FirstName)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(LastName);
}
五. 注意不能把 record 當作一定是 immutable(不可變)
,原因在微軟沒有限制
以下寫法...
public record record2
{
public string FirstName {get;set;}
public string LastName{get;set;}
}
准許修改 {get;init;}
為 {get;set}
,將會導致 immutable 跟 thread-safe 特性消失
六. record 會幫忙生成可讀性好的 ToString 實作
以下圖片為比較一般 class 跟 record 生成的 ToString 差別
七. record 幫忙生成 extend IEquatable<類別>
,並實作強型別public virtual bool Equals(Record1? other)
這代表可以避免原本public override bool Equals(object? obj)
需要先 unboxing 再 boxing 的效能損耗
問題
閱讀資料: