C#7.0 新增功能

C# 7.0 向 C# 語言添加了許多新功能html

01 out 變量
支持 out 參數的現有語法已在此版本中獲得改進。 如今能夠在方法調用的參數列表中聲明 out 變量,而不是編寫單獨的聲明語句:
if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else
    Console.WriteLine("Could not parse input");
爲清晰明瞭,可能需指定 out 變量的類型,如上所示。 可是,該語言支持使用隱式類型的局部變量:
if (int.TryParse(input, out var answer))
    Console.WriteLine(answer);
else
    Console.WriteLine("Could not parse input");
  • 代碼更易於閱讀。
    • 在使用 out 變量的地方聲明 out 變量,而不是在上面的另外一行。
  • 無需分配初始值。
    • 經過在方法調用中使用 out 變量的位置聲明該變量,使得在分配它以前不可能意外使用它。
02 元組
C# 爲用於說明設計意圖的類和結構提供了豐富的語法。 可是,這種豐富的語法有時會須要額外的工做,但益處卻不多。 你可能常常編寫須要包含多個數據元素的簡單結構的方法。爲了支持這些方案,已將元組 添加到了 C#。 元組是包含多個字段以表示數據成員的輕量級數據結構。 這些字段沒有通過驗證,而且你沒法定義本身的方法

低於 C# 7.0 的版本中也提供元組,但它們效率低下且不具備語言支持。 這意味着元組元素只能做爲 Item1 和 Item2 等引用。 C# 7.0 引入了對元組的語言支持,可利用更有效的新元組類型向元組字段賦予語義名稱。 c++

能夠經過爲每一個成員賦值來建立元組,並可選擇爲元組的每一個成員提供語義名稱:
(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");

namedLetters 元組包含稱爲 Alpha 和 Beta 的字段。 這些名稱僅存在於編譯時且不保留,例如在運行時使用反射來檢查元組時。git

在進行元組賦值時,還能夠指定賦值右側的字段的名稱:程序員

var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

在某些時候,你可能想要解包從方法返回的元組的成員。 可經過爲元組中的每一個值聲明單獨的變量來實現此目的。 這種解包操做稱爲解構元組github

(int max, int min) = Range(numbers);
Console.WriteLine(max);
Console.WriteLine(min);

還能夠爲 .NET 中的任何類型提供相似的析構。 編寫 Deconstruct 方法,用做類的成員。Deconstruct 方法爲你要提取的每一個屬性提供一組 out 參數。 考慮提供析構函數方法的此 Point 類,該方法提取 X 和 Y 座標:算法

public class Point
   {
       public double X { get; }
       public double Y { get; }
       
       public Point(double x, double y) => (X, Y) = (x, y);

       public void Deconstruct(out double x, out double y) => (x, y) = (X, Y);
   }

能夠經過向元組分配 Point 來提取各個字段:express

var p = new Point(3.14, 2.71);
(
double X, double Y) = p;

可在元組相關文章中深刻了解有關元組的詳細信息。編程

03 棄元

一般,在進行元組解構或使用 out 參數調用方法時,必須定義一個其值可有可無且你不打算使用的變量。 爲處理此狀況,C# 增添了對棄元的支持 。 棄元是一個名爲 _(下劃線字符)的只寫變量,可向單個變量賦予要放棄的全部值。 棄元相似於未賦值的變量;不可在代碼中使用棄元(賦值語句除外)。api

在如下方案中支持棄元:數組

  • 在對元組或用戶定義的類型進行解構時。
  • 在使用 out 參數調用方法時。
  • 在使用 is 和 switch 語句匹配操做的模式中。
  • 在要將某賦值的值顯式標識爲棄元時用做獨立標識符。

如下示例定義了 QueryCityDataForYears 方法,它返回一個包含兩個不一樣年份的城市數據的六元組。 本例中,方法調用僅與此方法返回的兩我的口值相關,所以在進行元組解構時,將元組中的其他值視爲棄元。

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 public class Example
 5 {
 6    public static void Main()
 7    {
 8        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
 9 
10        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
11    }
12    
13    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
14    {
15       int population1 = 0, population2 = 0;
16       double area = 0;
17       
18       if (name == "New York City") {
19          area = 468.48; 
20          if (year1 == 1960) {
21             population1 = 7781984;
22          }
23          if (year2 == 2010) {
24             population2 = 8175133;
25          }
26       return (name, area, year1, population1, year2, population2);
27       }
28 
29       return ("", 0, 0, 0, 0, 0);
30    }
31 }
32 //  輸出結果:
33 //  Population change, 1960 to 2010: 393,149

有關詳細信息,請參閱棄元

04 模式匹配

模式匹配 是一種可以讓你對除對象類型之外的屬性實現方法分派的功能。 你可能已經熟悉基於對象類型的方法分派。 在面向對象的編程中,虛擬和重寫方法提供語言語法來實現基於對象類型的方法分派。 基類和派生類提供不一樣的實現。 模式匹配表達式擴展了這一律念,以便你能夠經過繼承層次結構爲不相關的類型和數據元素輕鬆實現相似的分派模式。

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

is 模式表達式擴展了經常使用 is 運算符以查詢關於其類型的對象,並在一條指令分配結果。如下代碼檢查變量是否爲 int,若是是,則將其添加到當前總和:

if (input is int count)
    sum += count;

前面的小型示例演示了 is 表達式的加強功能。 能夠針對值類型和引用類型進行測試,而且能夠將成功結果分配給類型正確的新變量。

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: 是常見的默認事例。

能夠在 C# 中的模式匹配中瞭解有關模式匹配的更多信息。

05 Ref 局部變量和返回結果
此功能容許使用並返回對變量的引用的算法,這些變量在其餘位置定義。 一個示例是使用大型矩陣並查找具備某些特徵的單個位置。 下面的方法在矩陣中向該存儲返回「引用」 :
    /// <summary>
    ///  Ref局部變量和返回結果
    /// </summary>
    public class MatrixSearch
    {
        public static ref int Find(int[,] matrix, Func<int, bool> predicate)
        {
            for (int i = 0; i < matrix.GetLength(0); i++)
            {
                for (int j = 0; j < matrix.GetLength(1); j++)
                {
                    if (predicate(matrix[i, j]))
                    {
                        return ref matrix[i, j];
                    }
                }
            }
            throw new InvalidOperationException("Not found");
        }
    }

能夠將返回值聲明爲 ref 並在矩陣中修改該值,如如下代碼所示:

 int[,] matrix = new int[5,6];
 ref var item = ref MatrixSearch.Find(matrix, (val) => val == 42);
 Console.WriteLine(item);
 item = 24;
 Console.WriteLine(matrix[4, 2]);

C# 語言還有多個規則,可保護你免於誤用 ref 局部變量和返回結果:

  • 必須將 ref 關鍵字添加到方法簽名和方法中的全部 return 語句中。
    • 這清楚地代表,該方法在整個方法中經過引用返回。
  • 能夠將 ref return 分配給值變量或 ref 變量。
    • 調用方控制是否複製返回值。 在分配返回值時省略 ref 修飾符表示調用方須要該值的副本,而不是對存儲的引用。
  • 不可向 ref 本地變量賦予標準方法返回值。
    • 由於那將禁止相似 ref int i = sequence.Count(); 這樣的語句
  • 不能將 ref 返回給其生存期不超出方法執行的變量。
    • 這意味着不可返回對本地變量或對相似做用域變量的引用。
  • ref 局部變量和返回結果不可用於異步方法。
    • 編譯器沒法知道異步方法返回時,引用的變量是否已設置爲其最終值。

添加 ref 局部變量和 ref 返回結果可經過避免複製值或屢次執行取消引用操做,容許更爲高效的算法。

向返回值添加 ref 是源兼容的更改。 現有代碼會進行編譯,但在分配時複製 ref 返回值。調用方必須將存儲的返回值更新爲 ref 局部變量,從而將返回值存儲爲引用。

有關詳細信息,請參閱 ref 關鍵字一文。

06 本地函數

許多類的設計都包括僅從一個位置調用的方法。 這些額外的私有方法使每一個方法保持小且集中。 本地函數使你可以在另外一個方法的上下文內聲明方法 。 本地函數使得類的閱讀者更容易看到本地方法僅從聲明它的上下文中調用。

對於本地函數有兩個常見的用例:公共迭代器方法和公共異步方法。 這兩種類型的方法都生成報告錯誤的時間晚於程序員指望時間的代碼。 在迭代器方法中,只有在調用枚舉返回的序列的代碼時纔會觀察到任何異常。 在異步方法中,只有當返回的 Task 處於等待狀態時纔會觀察到任何異常。 如下示例演示如何使用本地函數將參數驗證與迭代器實現分離:

 1  public static IEnumerable<char> AlphabetSubset3(char start, char end)
 2         {
 3             if (start < 'a' || start > 'z')
 4                 throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
 5             if (end < 'a' || end > 'z')
 6                 throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");
 7 
 8             if (end <= start)
 9                 throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
10 
11             return AlphabetSubsetImplementation();
12 
13             IEnumerable<char> AlphabetSubsetImplementation()
14             {
15                 for (var c = start; c < end; c++)
16                 {
17                     yield return c;
18                 }
19             }
20         }

能夠對 async 方法採用相同的技術,以確保在異步工做開始以前引起由參數驗證引發的異常:

 1  public Task<string> PerformLongRunningWork(string address, int index, string name)
 2         {
 3             if (string.IsNullOrWhiteSpace(address))
 4                 throw new ArgumentException(message: "An address is required", paramName: nameof(address));
 5             if (index < 0)
 6                 throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
 7             if (string.IsNullOrWhiteSpace(name))
 8                 throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));
 9 
10             return LongRunningWorkImplementation();
11 
12             async Task<string> LongRunningWorkImplementation()
13             {
14                 var interimResult = await FirstWork(address);
15                 var secondResult = await SecondStep(index, name);
16                 return $"The results are {interimResult} and {secondResult}. Enjoy.";
17             }
18         }
19 
20         private async Task<string> FirstWork(string address)
21         {
22             // await  ··· 業務邏輯
23             return "";
24         }
25 
26         private async Task<string> SecondStep(int index, string name)
27         {
28             // await  ··· 業務邏輯
29             return "";
30         }

本地函數支持的某些設計也可使用 lambda 表達式 來完成。 感興趣的能夠閱讀有關差別的詳細信息

07 更多的 expression-bodied 成員
C# 6 爲成員函數和只讀屬性引入了 expression-bodied 成員。 C# 7.0 擴展了可做爲表達式實現的容許的成員。 在 C# 7.0 中,你能夠在屬性 和索引器 上實現構造函數 、終結器 以及 get 和 set 訪問器。 如下代碼演示了每種狀況的示例:
 public class ExpressionMembersExample
    {
        // Expression-bodied 構造函
        public ExpressionMembersExample(string label) => this.Label = label;

        // Expression-bodied 終結器
        ~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

        private string label;

        // Expression-bodied get / set 
        public string Label
        {
            get => label;
            set => this.label = value ?? "Default label";
        }
    }

本示例不須要終結器,但顯示它是爲了演示語法。 不該在類中實現終結器,除非有必要發佈非託管資源。 還應考慮使用 SafeHandle 類,而不是直接管理非託管資源。

這些 expression-bodied 成員的新位置表明了 C# 語言的一個重要里程碑:這些功能由致力於開發開放源代碼 Roslyn 項目的社區成員實現。

將方法更改成 expression bodied 成員是二進制兼容的更改

08 引起表達式

在 C# 中,throw 始終是一個語句。 由於 throw 是一個語句而非表達式,因此在某些 C# 構造中沒法使用它。 它們包括條件表達式、null 合併表達式和一些 lambda 表達式。 添加 expression-bodied 成員將添加更多位置,在這些位置中,throw 表達式會頗有用。 爲了能夠編寫這些構造,C# 7.0 引入了 throw 表達式。這使得編寫更多基於表達式的代碼變得更容易。 不須要其餘語句來進行錯誤檢查。

從 C# 7.0 開始,throw 能夠用做表達式和語句。 這容許在之前不支持的上下文中引起異常。 這些方法包括:

  • 條件運算符。 下例使用 throw 表達式在向方法傳遞空字符串數組時引起 ArgumentException。 在 C# 7.0 以前,此邏輯將須要顯示在 if/else 語句中。

 private static void DisplayFirstNumber(string[] args)
        {
            string arg = args.Length >= 1 ? args[0] : throw new ArgumentException("You must supply an argument");

            if (Int64.TryParse(arg, out var number))
            {
                Console.WriteLine($"You entered {number:F0}");
            }
            else
            {
                Console.WriteLine($"{arg} is not a number.");
            }

        }
  • null 合併運算符。 在如下示例中,若是分配給 Name 屬性的字符串爲 null,則將 throw 表達式與 null 合併運算符結合使用以引起異常。
public string Name
        {
            get => name;
            set => name = value ??
                          throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
        }
  • expression-bodied lambda 或方法。 下例說明了 expression-bodied 方法,因爲不支持對 DateTime 值的轉換,該方法引起 InvalidCastException
DateTime ToDateTime(IFormatProvider provider) => 
         throw new InvalidCastException("Conversion to a DateTime is not supported.");
09 通用的異步返回類型

從異步方法返回 Task 對象可能在某些路徑中致使性能瓶頸。 Task 是引用類型,所以使用它意味着分配對象。 若是使用 async 修飾符聲明的方法返回緩存結果或以同步方式完成,那麼額外的分配在代碼的性能關鍵部分可能要耗費至關長的時間。 若是這些分配發生在緊湊循環中,則成本會變高。

新語言功能意味着異步方法返回類型不限於 TaskTask<T> 和 void。 返回類型必須仍知足異步模式,這意味着 GetAwaiter 方法必須是可訪問的。 做爲一個具體示例,已將 ValueTask 類型添加到 .NET framework 中,以使用這一新語言功能:

 public async ValueTask<int> Func()
 {
    await Task.Delay(100);
    return 5;
 }

須要添加 NuGet 包 System.Threading.Tasks.Extensions 才能使用 ValueTask 類型。

此加強功能對於庫做者最有用,可避免在性能關鍵型代碼中分配 Task

10 數字文本語法改進

誤讀的數值常量可能使第一次閱讀代碼時更難理解。 位掩碼或其餘符號值容易產生誤解。C# 7.0 包括兩項新功能,可用於以最可讀的方式寫入數字來用於預期用途:二進制文本和數字分隔符 。

在建立位掩碼時,或每當數字的二進制表示形式使代碼最具可讀性時,以二進制形式寫入該數字:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

常量開頭的 0b 表示該數字以二進制數形式寫入。 二進制數可能會很長,所以經過引入 _做爲數字分隔符一般更易於查看位模式,如上面二進制常量所示。 數字分隔符能夠出如今常量的任何位置。 對於十進制數字,一般將其用做千位分隔符:

public const long BillionsAndBillions = 100_000_000_000;

數字分隔符也能夠與 decimalfloat 和 double 類型一塊兒使用:

public const double  AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio =      1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

綜觀來講,你能夠聲明可讀性更強的數值常量。

相關文章
相關標籤/搜索