C# 7.0 向 C# 語言添加了許多新功能html
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
變量的位置聲明該變量,使得在分配它以前不可能意外使用它。低於 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;
可在元組相關文章中深刻了解有關元組的詳細信息。編程
一般,在進行元組解構或使用 out
參數調用方法時,必須定義一個其值可有可無且你不打算使用的變量。 爲處理此狀況,C# 增添了對棄元的支持 。 棄元是一個名爲 _
(下劃線字符)的只寫變量,可向單個變量賦予要放棄的全部值。 棄元相似於未賦值的變量;不可在代碼中使用棄元(賦值語句除外)。api
在如下方案中支持棄元:數組
如下示例定義了 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
有關詳細信息,請參閱棄元。
模式匹配 是一種可以讓你對除對象類型之外的屬性實現方法分派的功能。 你可能已經熟悉基於對象類型的方法分派。 在面向對象的編程中,虛擬和重寫方法提供語言語法來實現基於對象類型的方法分派。 基類和派生類提供不一樣的實現。 模式匹配表達式擴展了這一律念,以便你能夠經過繼承層次結構爲不相關的類型和數據元素輕鬆實現相似的分派模式。
模式匹配支持 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# 中的模式匹配中瞭解有關模式匹配的更多信息。
/// <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 關鍵字一文。
許多類的設計都包括僅從一個位置調用的方法。 這些額外的私有方法使每一個方法保持小且集中。 本地函數使你可以在另外一個方法的上下文內聲明方法 。 本地函數使得類的閱讀者更容易看到本地方法僅從聲明它的上下文中調用。
對於本地函數有兩個常見的用例:公共迭代器方法和公共異步方法。 這兩種類型的方法都生成報告錯誤的時間晚於程序員指望時間的代碼。 在迭代器方法中,只有在調用枚舉返回的序列的代碼時纔會觀察到任何異常。 在異步方法中,只有當返回的 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 表達式 來完成。 感興趣的能夠閱讀有關差別的詳細信息
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 成員是二進制兼容的更改。
在 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."); } }
Name
屬性的字符串爲 null
,則將 throw
表達式與 null 合併運算符結合使用以引起異常。public string Name { get => name; set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); }
DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
從異步方法返回 Task
對象可能在某些路徑中致使性能瓶頸。 Task
是引用類型,所以使用它意味着分配對象。 若是使用 async
修飾符聲明的方法返回緩存結果或以同步方式完成,那麼額外的分配在代碼的性能關鍵部分可能要耗費至關長的時間。 若是這些分配發生在緊湊循環中,則成本會變高。
新語言功能意味着異步方法返回類型不限於 Task
、Task<T>
和 void
。 返回類型必須仍知足異步模式,這意味着 GetAwaiter
方法必須是可訪問的。 做爲一個具體示例,已將 ValueTask
類型添加到 .NET framework 中,以使用這一新語言功能:
public async ValueTask<int> Func() { await Task.Delay(100); return 5; }
須要添加 NuGet 包 System.Threading.Tasks.Extensions 才能使用 ValueTask 類型。
此加強功能對於庫做者最有用,可避免在性能關鍵型代碼中分配 Task
。
誤讀的數值常量可能使第一次閱讀代碼時更難理解。 位掩碼或其餘符號值容易產生誤解。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;
數字分隔符也能夠與 decimal
、float
和 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;
綜觀來講,你能夠聲明可讀性更強的數值常量。