說說 C# 9 新特性的實際運用

你必定會好奇:「老周,你去哪開飛機了?這麼久沒寫博客了。」函數

老周:「我買不起飛機,開了個鐵礦,挖了一年半的石頭。誰知鐵礦垮了,壓死了幾條蜈蚣,什麼也沒挖着。」學習

因此,這麼丟死人的事,仍是不要提了,爺爺從小教導我作人要低調……spa

 

一轉眼,.NET 5 要來了,同時也帶來了 C# 9。遙想當年,老周剛接觸 .NET 1.1 的時候,纔剛上大學;現在已通過去13年了。歲月是把水果刀,歷來不饒人啊。翻譯

老周不多去寫諸如「XXX新特性」之類的文章,總以爲沒啥用處。不過,針對 C# 9,老周想說一點什麼。code

好,在開始以前,老周再次強調一下:這些語言新特性的東西,你千萬不要特地去學習,千萬不要,不要,不要,重要的事情講四遍!這些玩意兒你只要看看官方給的說明,刷一遍就能掌握了(刷這個比刷抖音有意義多了),不用去學的。若是你連這些東東也要學習成本的話,我只想說句好唱很差聽的話——你的學習能力真的值得懷疑對象

 

好了,下面開始表演。blog

第一齣:record 類型

record ,我仍是用原詞吧,我知道有翻譯爲「記錄類型」的說法。只是,只是,老周老以爲這不太好聽,但是老周也找不出更好的詞語,仍是用回 record吧。ci

record 是引用類型,跟 class 很像(確實差很少)。那麼,用人民羣衆都熟悉的 class 不香嗎,爲什麼要新增個 record 呢?答:爲了數據比較的便捷。get

不明白?沒事,往下看。最近有一位熱心鄰居送了老週一只寵物:編譯器

    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;

猜猜啥意思?這個是能夠「望文生 yi」的,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);

這時候你得注意:

1)switch 如今是表達式,不是語句塊,因此最後大括號右邊的分號不能少;

2)由於 switch 成了表達式,就不能用 case 子句了,因此直接用具體的內容來匹配;

3)最後返回「未知」的那個下劃線(_),也就是所謂的「棄嬰」,哦不,是「棄元」,就是雖然賦了值但不須要使用的變量,能夠直接丟掉。這裏就至關於 switch 語句塊中的 default 子句,當前面全部條件都不能匹配時,就返回「未知」。

 

第三齣:屬性的 init 訪問器

要首先得知道,這個 init 只用於只讀屬性的初始化階段,對於可讀可寫的屬性,和之前同樣,直接 get; set; 便可。

有人說這個 int 不知幹啥用,那好,我們先不說它,先來看看 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# 9 新特性用法的一些不成文的闡述。看完後你就別說難了。

相關文章
相關標籤/搜索