第十九節:語法總結(4)之C# 7.x、8.0、9.0新語法


一. C#7.x總結

參考:https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-7

1.棄元

(1).含義

  從 C# 7.0 開始,C# 支持棄元,這是一種在應用程序代碼中人為取消使用的【占位符變量】、。 棄元相當於未賦值的變量;它們沒有值。 因為只有一個棄元變量,甚至不為該變量分配存儲空間,所以棄元可減少內存分配。 因為它們使代碼的意圖清楚,增強了其可讀性和可維護性

(2). 使用場景

 A. 元組和對象析構。 

public class Example
{
    public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;

        if (name == "New York City")
        {
            area = 468.48;
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }
}
View Code

 B. 使用 is 和 switch 的模式匹配。

public class Example
{
   public static void Main()
   {
      object[] objects = { CultureInfo.CurrentCulture,
                           CultureInfo.CurrentCulture.DateTimeFormat,
                           CultureInfo.CurrentCulture.NumberFormat,
                           new ArgumentException(), null };
      foreach (var obj in objects)
         ProvidesFormatInfo(obj);
   }

   private static void ProvidesFormatInfo(object obj)
   {
      switch (obj)
      {
         case IFormatProvider fmt:
            Console.WriteLine($"{fmt} object");
            break;
         case null:
            Console.Write("A null object reference: ");
            Console.WriteLine("Its use could result in a NullReferenceException");
            break;
         case object _:
            Console.WriteLine("Some object type without format information");
            break;
      }
   }
}
View Code

 C. 使用out參數的棄元 DateTime.TryParse(String, out DateTime)

public class Example
{
   public static void Main()
   {
      string[] dateStrings = {"05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
                              "2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
                              "5/01/2018 14:57:32.80 -07:00",
                              "1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
                              "Fri, 15 May 2018 20:10:57 GMT" };
      foreach (string dateString in dateStrings)
      {
         if (DateTime.TryParse(dateString, out _))
            Console.WriteLine($"'{dateString}': valid");
         else
            Console.WriteLine($"'{dateString}': invalid");
      }
   }
}
View Code

 D. 獨立棄元。 可使用獨立棄元來指示要忽略的任何變量。 以下示例使用獨立占位符來忽略異步操作返回的 Task 對象。

private static async Task ExecuteAsyncMethods()
   {
      Console.WriteLine("About to launch a task...");
      _ = Task.Run(() => { var iterations = 0;
                           for (int ctr = 0; ctr < int.MaxValue; ctr++)
                              iterations++;
                           Console.WriteLine("Completed looping operation...");
                           throw new InvalidOperationException();
                         });
      await Task.Delay(5000);
      Console.WriteLine("Exiting after 5 second delay");
   }
View Code

 E. C# 9.0 開始,可以使用棄元指定 Lambda 表達式中不使用的輸入參數

2. 元祖

  詳見:https://www.cnblogs.com/yaopengfei/p/9061819.html

3. 模式匹配

 模式匹配支持 is 表達式和 switch 表達式。 每個表達式都允許檢查對象及其屬性以確定該對象是否滿足所尋求的模式。 使用 when 關鍵字來指定模式的其他規則。

  switch 匹配表達式具有常見的語法,它基於已包含在 C# 語言中的 switch 語句。 更新后的 switch 語句有幾個新構造:

  switch 表達式的控制類型不再局限於整數類型、Enum 類型、string 或與這些類型之一對應的可為 null 的類型。 可能會使用任何類型。

  可以在每個 case 標簽中測試 switch 表達式的類型。 與 is 表達式一樣,可以為該類型指定一個新變量。

  可以添加 when 子句以進一步測試該變量的條件。

  case 標簽的順序現在很重要。 執行匹配的第一個分支;其他將跳過。

代碼分享:

public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
    int sum = 0;
    foreach (var i in sequence)
    {
        switch (i)
        {
            case 0:
                break;
            case IEnumerable<int> childSequence:
            {
                foreach(var item in childSequence)
                    sum += (item > 0) ? item : 0;
                break;
            }
            case int n when n > 0:
                sum += n;
                break;
            case null:
                throw new NullReferenceException("Null found in sequence");
            default:
                throw new InvalidOperationException("Unrecognized type");
        }
    }
    return sum;
}

剖析:

  • case 0: 是常見的常量模式。
  • case IEnumerable<int> childSequence: 是一種類型模式。
  • case int n when n > 0: 是具有附加 when 條件的類型模式。
  • case null: 是 null 模式。
  • default: 是常見的默認事例。

4. 異步 main 方法

 支持:static async Task<int> Main(){};

 支持:static async Task Main(){};

5. 本地函數

 方法中再聲明一個方法

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

7. 默認委托表達式

舊寫法

Func<string, bool> whereClause = default(Func<string, bool>);

新寫法

Func<string, bool> whereClause = default;

8. 數字語法改進

  增強可讀性 public const long BillionsAndBillions = 100_000_000_000;

9. out變量

 可以在方法調用的參數列表中聲明 out 變量,而不是編寫單獨的聲明語句,不需要先單獨聲明了

if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else
    Console.WriteLine("Could not parse input");

10. in參數修飾符

 in 關鍵字補充了現有的 ref 和 out 關鍵字,以按引用傳遞參數。 in 關鍵字指定按引用傳遞參數,但調用的方法不修改值。

 static void M(S arg);

 static void M(in S arg);

11. stackalloc 數組支持初始值設定項

詳見下面代碼

int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};

 

二. C#8.0總結

 (.NET Core 3.x和.NET Standard 2.1支持 C# 8.0 )

1.模式匹配

(1).Switch表達式

 A.變量位於 switch 關鍵字之前。 不同的順序使得在視覺上可以很輕松地區分 switch 表達式和 switch 語句。

 B.將 case 和 : 元素替換為 =>。 它更簡潔,更直觀。

 C.將 default 事例替換為 _ 棄元。

 D.正文是表達式,不是語句。

public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

(2).屬性模式

 借助屬性模式,可以匹配所檢查的對象的屬性。

      /// <summary>
        /// 屬性模式
        /// state是Adress的屬性
        /// </summary>
        /// <param name="location"></param>
        /// <param name="salePrice"></param>
        /// <returns></returns>
        public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
        location switch
        {
            { state: "WA" } => salePrice * 0.06M,
            { state: "MN" } => salePrice * 0.075M,
            { state: "MI" } => salePrice * 0.05M,
            // other cases removed for brevity...
            _ => 0M
        };
 public class Address
    {
        public string state { get; set; }

        public string xxx1 { get; set; }

        public string xxx2 { get; set; }
    }
View Code

(3).元組模式

 一些算法依賴於多個輸入。 使用元組模式,可根據表示為元組的多個值進行切換。 以下代碼顯示了游戲“rock, paper, scissors(石頭剪刀布)”的切換表達式::

       /// <summary>
        /// 元祖模式
        /// </summary>
        /// <param name="first"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        public static string RockPaperScissors(string first, string second)
        => (first, second) switch
        {
            ("rock", "paper") => "rock is covered by paper. Paper wins.",
            ("rock", "scissors") => "rock breaks scissors. Rock wins.",
            ("paper", "rock") => "paper covers rock. Paper wins.",
            ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
            ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
            ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
            (_, _) => "tie"
        };

(4).位置模式

代碼分享

static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

 2. Using聲明

 可以省略(){},它指示編譯器聲明的變量應在封閉范圍的末尾進行處理.

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    int skippedLines = 0;
    foreach (string line in lines)
    {
        if (!line.Contains("Second"))
        {
            file.WriteLine(line);
        }
        else
        {
            skippedLines++;
        }
    }
    // Notice how skippedLines is in scope here.
    return skippedLines;
    // file is disposed here
}
static int WriteLinesToFile(IEnumerable<string> lines)
{
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        int skippedLines = 0;
        foreach (string line in lines)
        {
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
            else
            {
                skippedLines++;
            }
        }
        return skippedLines;
    } // file is disposed here
}
View Code

3.靜態本地函數

 可以在方法內部聲明一個內部的靜態方法。

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

4.可以設置空引用類型

  通過? 符號來設置

5.索引和范圍

 (1).末尾運算符 ^ 的索引,指定一個索引與序列末尾相關.

     ^1表示倒數第一個元素。 ^2表示倒數第二個元素

 (2).范圍運算符 ..,用於指定范圍的開始和末尾,就像操作數一樣。 也可以聲明變量: Range r1 = 1..4;

            {
                var words = new string[]
                {
                                // index from start    index from end
                    "The",      // 0                   ^9
                    "quick",    // 1                   ^8
                    "brown",    // 2                   ^7
                    "fox",      // 3                   ^6
                    "jumped",   // 4                   ^5
                    "over",     // 5                   ^4
                    "the",      // 6                   ^3
                    "lazy",     // 7                   ^2
                    "dog"       // 8                   ^1
                };              // 9 (or words.Length) ^0

                //第一個元素
                Console.WriteLine($"第一個元素為:{words[0]}");
                //倒數第一、二個元素
                Console.WriteLine($"倒數第一個元素為:{words[^1]}");
                Console.WriteLine($"倒數第二個元素為:{words[^2]}");

                //范圍
                var p1 = words[..];   // 全部元素
                var p2 = words[..4];  // 前4個元素
                var p3 = words[6..];  // 從第7個元素到最后
                var p4 = words[1..4]; //獲取第2,3,4個元素

                //也可以先聲明變量
                Range r1 = 1..4;
                var p5 = words[r1];

            }

6.Null合並賦值

 C# 8.0 引入了 null 合並賦值運算符 ??=。 僅當左操作數計算為 null 時,才能使用運算符 ??= 將其右操作數的值分配給左操作數。

            {
                List<int> numbers = null;
                int? i = null;

                numbers ??= new List<int>();
                numbers.Add(i ??= 17);
                numbers.Add(i ??= 20);

                //最終輸出結果是 兩個 17
                foreach (var item in numbers)
                {
                    Console.WriteLine(item);
                }

            }

7.內插逐字字符串的增強功能

 內插逐字字符串中 $ 和 @ 標記的順序可以任意安排:$@"..." 和 @$"..." 均為有效的內插逐字字符串。 在早期 C# 版本中,$ 標記必須出現在 @ 標記之前。

  {
                string str = "ypf";
                string a1 = $@"lskdfjslf{str}slkfslf";
                string a2 = @$"lskdfjslf{str}slkfslf";
  }

 

三. C#9.0總結

(.Net 5.x 支持C# 9.0)

1.記錄類型

 C# 9.0 引入了記錄類型,這是一種引用類型,它提供合成方法來提供值語義,從而實現相等性。 默認情況下,記錄是不可變的。使用記錄類型可在.NET 中輕松創建不可變的引用類型

public record Person
{
    public string LastName { get; }
    public string FirstName { get; }

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

2.頂級語句

 System.Console.WriteLine("Hello World!");可以刪除 using System;

3.模式匹配增強功能

 類型模式要求在變量是一種類型時匹配

 帶圓括號的模式強制或強調模式組合的優先級

 聯合 and 模式要求兩個模式都匹配

 析取 or 模式要求任一模式匹配

 求反 not 模式要求模式不匹配

 關系模式要求輸入小於、大於、小於等於或大於等於給定常數。

        //模式匹配增強
 public  bool IsLetter(char c) =>
     c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
 public  bool IsLetterOrSeparator(char c) =>
     c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

4.調整和完成功能

 在 C# 9.0 中,已知創建對象的類型時,可在 new 表達式中省略該類型

 如:List<string> list1 = new();

private List<WeatherObservation> _observations = new();
WeatherStation station = new() { Location = "Seattle, WA" };
List<string> list1 = new();

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM