C#9特性record 類型、模式匹配、init 屬性詳情
C#的特性record 類型、模式匹配、init 屬性
一、record 類型
record ,我還是用原詞吧,我知道有翻譯為“記錄類型”的說法。隻是,隻是,老周老覺得這不太好聽,可是老周也找不出更好的詞語,還是用回 record吧。
record
是引用類型
,跟 class 很像(確實差不多)。那麼,用人民群眾都熟悉的 class 不香嗎,為何要新增個 record 呢?答:為瞭數據比較的便捷。
不明白?沒事,往下看。最近有一位熱心鄰居送瞭老周一隻寵物:
public class Cat { public string Nick { get; set; } public string Name { get; set; } public int Age { get; set; } }
這隻新寵物可不簡單,一頂一的高級吃貨。魚肉、豬肉、雞腿、餅幹、豆腐、面包、水果、面條、小麥、飛蛾……反正,隻要它能塞進嘴裡的,它都吃。
接下來,我們 new 兩個寵物實例。
// 兩個實例描述的是同一隻貓 Cat pet1 = new Cat { Nick = "松子", Name = "Jack", Age = 1 }; Cat pet2 = new Cat { Nick = "松子", Name = "Jack", Age = 1 }; // 居然不是同一隻貓 Console.WriteLine("同一隻?{0}", pet1 == pet2);
其實,兩個實例描述的都是我傢的乖乖。可是,輸出的是:
同一隻?False
這是因為,在相等比較時,人傢關心的類型引用——引用的是否為同一個實例。但是,在數據處理方案中,我們更關註對象中的字段/屬性是否相等,即內容比較。
現在,把 Cat 的聲明改為 record 類型。
public record Cat { public string Nick { get; set; } public string Name { get; set; } public int Age { get; set; } }
然後同樣用上面的 pet1 和 pet2 實例進行相等比較,得到預期的結果:
同一隻?True
record 類型讓你省去瞭重寫相等比較(重寫 Equals、GetHashCode 等方法或重載運算符)的邏輯。
實際上,代碼在編譯後 record 類型也是一個類,但自動實現瞭成員相等比較的邏輯。以前你要手動去折騰的事現在全交給編譯器去幹。
假如,有一個 User 類型,用於表示用戶信息(包括用戶名、密碼),然後這個 User 類型在數據處理方案中可能會產生N多個實例。例如你根據條件從EF模型中篩選出一個 User 實例 A,根據用戶輸入的登錄名和密碼產生瞭 User 實例 B。為瞭驗證用戶輸入的登錄信息是否正確,如果 User 是 class,你可能要這樣判斷:
if(A.UserName == B.UserName && A.Password == B.Password) { .................. }
但要是你把 User 定義為 record 類型,那麼,一句話的工夫:
A == B
二、模式匹配(Pattern Matching)
“模式匹配”這個翻譯感覺怪怪滴,老周還沒想出什麼更好的詞語。模式匹配並不是什麼神奇的東西,它隻是在對變量值進行檢測時的擴展行為。以前,老感覺C++/C# 的 switch 語句不夠強大,因為傳統的用法裡面,每個 case 子句隻能比較單個常量值。比如
int 考試成績 = 85; switch (考試成績) { case 10: Console.WriteLine("才考這麼點破分啊"); break; case 50: Console.WriteLine("還差一點,就合格瞭"); break; case 85: Console.WriteLine("真是秀"); break; case 90: Console.WriteLine("奇跡發生"); break; }
我幻想著,要是能像下面這樣寫就好瞭:
switch (考試成績) { case 0: Console.WriteLine("缺考?"); break; case > 0 && <= 30: Console.WriteLine("太爛瞭"); break; case > 30 && < 60: Console.WriteLine("還是不行"); break; case >= 60 && < 80: Console.WriteLine("還得努力"); break; case >= 80 && < 90: Console.WriteLine("秀兒,真優秀"); break; case >= 90 && <= 100: Console.WriteLine("不錯,奇跡"); break; }
等瞭很多年很多年(“千年等一回,等……”)以後,終於可以實現瞭。
switch (考試成績) { case 0: Console.WriteLine("缺考?"); break; case > 0 and <= 30: Console.WriteLine("太爛瞭"); break; case > 30 and < 60: Console.WriteLine("還是不行"); break; case >= 60 and < 80: Console.WriteLine("還得努力"); break; case >= 80 and < 90: Console.WriteLine("秀兒,真優秀"); break; case >= 90 and <= 100: Console.WriteLine("不錯,奇跡"); break; }
有時候,不僅要檢測對象的值,還得深入到其成員。比如下面這個例子,Order類表示一條訂單信息。
public class Order { public int ID { get; set; } public string Company { get; set; } public string ContactName { get; set; } public float Qty { get; set; } public decimal UP { get; set; } public DateTime Date { get; set; } }
前不久,公司接到一筆Order,做成瞭收益應該不錯。
Order od = new Order { ID = 11, Company = "大嘴狗貿易有限公司", ContactName = "陳大爺", Qty = 425.12f, UP = 1000.55M, Date = new(2020, 10, 27) };
假如我要在變量 od 上做 switch,看看,就這樣:
switch (od) { case { Qty: > 1000f }: Console.WriteLine("發財瞭,發財瞭"); break; case { Qty: > 500f }: Console.WriteLine("好傢夥,年度大訂單"); break; case { Qty: > 100f }: Console.WriteLine("訂單量不錯"); break; }
咦?這,這是什麼鬼?莫驚莫驚,這不是鬼。它的意思是判斷 Qty 屬性的值,如果訂單貨量大於 100 就輸出“訂單量不錯”;要是訂單貨量大於 1000,那就輸出“發財瞭,發財瞭”。
但你會說,這對大括號怎麼來的呢?還記得這種 LINQ 的寫法嗎?
from x in ... where x.A ... select new { Prop1 = ..., Prop2 = ..., ................ }
new { … } 是匿名類型實例,那如果是非匿名類型呢,看看前面的 Cat 實例初始化。
Cat { .......... }
這就對瞭,這對大括號就是構造某實例的成員值用的,所以,上面的 switch 語句其實是這樣寫的:
switch (od) { case Order{ Qty: > 1000f }: Console.WriteLine("發財瞭,發財瞭"); break; case Order{ Qty: > 500f }: Console.WriteLine("好傢夥,年度大訂單"); break; case Order{ Qty: > 100f }: Console.WriteLine("訂單量不錯"); break; }
Order{ … } 就是匹配一個 Order 對象實例,並且它的 Qty 屬性要符合 … 條件。由於變量 od 始終就是 Order 類型,所以,case 子句中的 Order 就省略瞭,變成
case { Qty: > 1000f }: Console.WriteLine("發財瞭,發財瞭"); break;
如果出現多個屬性,則表示為多個屬性設定匹配條件,它們之間是“且”的關系。比如
case { Qty: > 100f, Company: not null }: Console.WriteLine("訂單量不錯"); break;
猜猜啥意思?這個是可以“望文生義”的,Qty 屬性的值要大於 100,並且 Company 屬性的值不能為 null。不為 null 的寫法是 not null,不要寫成 !null,因為這樣太難看瞭。
如果你的代碼分支較少,你可以用 if 語句的,隻是得配合 is 運算符。
if (od is { UP: < 3000M }) { Console.WriteLine("報價不理想"); }
但是,這個寫法目前有局限性,它隻能用常量值來做判斷,你要是這樣寫就會報錯。
if (od is { Date: < DateTime.Now }) { ................ }
DateTime.Now 不是常量值,上面代碼無法通過編譯。
is 運算符以前是用來匹配類型的,上述的用法是它的語法擴展。
object n = 5000000L; if(n is long) { Console.WriteLine("它是個長整型"); }
進化之後的 is 運算符也可以這樣用:
object n = 5000000L; if(n is long x) { Console.WriteLine("它是個長整型,存放的值是:{0}", x); }
如果你在 if 語句內要使用 n 的值,就可以順便轉為 long 類型並賦值給變量 x,這樣就一步到位,不必再去寫一句 long x = (long)n 。
如果 switch… 語句在判斷之後需要返回一個值,還可以把它變成表達式來用。咱們把前面的 Order 例子改一下。
string message = od switch { { Qty: > 1000f } => "發財瞭", { Qty: > 500f } => "年度大訂單", { Qty: > 100f } => "訂單量不錯", _ => "未知" }; Console.WriteLine(message);
這時候你得註意:
- switch 現在是表達式,不是語句塊,所以最後大括號右邊的分號不能少;
- 因為 switch 成瞭表達式,就不能用 case 子句瞭,所以直接用具體的內容來匹配;
- 最後返回“未知”的那個下劃線(_),也就是所謂的“棄嬰”,哦不,是“棄元”,就是雖然賦瞭值但不需要使用的變量,可以直接丟掉。這裡就相當於 switch 語句塊中的 default 子句,當前面所有條件都不能匹配時,就返回“未知”。
三、屬性的 init 訪問器
要首先得知道,這個 init 隻用於隻讀屬性的初始化階段,對於可讀可寫的屬性,和以前一樣,直接 get
; set
; 即可。
有人說這個 init 不知幹啥用,那好,咱們先不說它,先來看看 C# 前些版本中新增的屬性初始化語句。
public class Dog { public int No { get; } = 0; public string Name { get; } = "no name"; public int Age { get; } = 1; }
你看,這樣就可以給屬性分配初始值瞭,那還要 init 幹嗎呢?
好,我給你制造一個問題——我要是這樣初始化 Dog 類的屬性,你試試看。
Dog x = new Dog { No = 100, Name = "吉吉", Age = 4 };
試一下,編譯會出錯吧。
有些情況,你可以在屬性定義階段分配初始值,但有些時候,你必須要在代碼中初始化。在過去,我們會通過定義帶參數的構造函數來解決。
public class Dog { public int No { get; } = 0; public string Name { get; } = "no name"; public int Age { get; } = 1; public Dog(int no, string name, int age) { No = no; Name = name; Age = age; } }
然後,這樣初始化。
Dog x = new(1001, "吉吉", 4);
可是,這樣做的裝逼指數依然不夠高,你總不能每個類都來這一招吧,雖然不怎麼辛苦,但每個類都得去寫一個構造函數,不利落。
於是,init 訪問器用得上瞭,咱們把 Dog 類改改。
public class Dog { public int No { get; init; } public string Name { get; init; } public int Age { get; init; } }
你不用再去寫帶參數的構造函數瞭,實例化時直接為屬性賦值。
Dog x = new Dog { No = 100, Name = "吉吉", Age = 4 };
這樣一來,這些隻讀屬性都有默認的初始值瞭。
當然,這個賦值隻在初始化過程中有效,初始化之後你再想改屬性的值,沒門!
x.Name = "冬冬"; //錯誤 x.Age = 10; //錯誤
以上就是C#的record 類型、模式匹配、init 屬性詳情的詳細內容,更多關於C#的record 類型、模式匹配、init 屬性的資料請關註WalkonNet其它相關文章!