C#8.0提供了許多加強功能html
readonly
修飾符應用於結構的任何成員。 它指示該成員不會修改狀態。 這比將 readonly
修飾符應用於 struct
聲明更精細。 請考慮如下可變結構:
public struct Point { public double X { get; set; } public double Y { get; set; } public double Distance => Math.Sqrt(X * X + Y * Y); public override string ToString() => $"({X}, {Y}) is {Distance} from the origin"; }
像大多數結構同樣, ToString()
方法不會修改狀態。 能夠經過將 readonly
修飾符添加到 ToString()
的聲明來對此進行指示:算法
public readonly override string ToString() => $"({X}, {Y}) is {Distance} from the origin";
上述更改會生成編譯器警告,由於 ToString
訪問 Distance
屬性,該屬性未標記爲 readonly
:express
warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'
須要建立防護性副本時,編譯器會發出警告。 Distance
屬性不會更改狀態,所以能夠經過將 readonly
修飾符添加到聲明來修復此警告:編程
public readonly double Distance => Math.Sqrt(X * X + Y * Y);
請注意,readonly
修飾符對於只讀屬性是必需的。 編譯器不會假設 get
訪問器不修改狀態;必須明確聲明 readonly
。 編譯器會強制實施如下規則:readonly
成員不修改狀態。除非刪除 readonly
修飾符,不然不會編譯如下方法:api
public readonly void Translate(int xOffset, int yOffset) { X += xOffset; Y += yOffset; }
經過此功能,能夠指定設計意圖,使編譯器能夠強制執行該意圖,並基於該意圖進行優化。數組
如今能夠將成員添加到接口,併爲這些成員提供實現。 藉助此語言功能,API 做者能夠將方法添加到之後版本的接口中,而不會破壞與該接口當前實現的源或二進制文件兼容性。 現有的實現繼承默認實現 。 此功能使 C# 與面向 Android 或 Swift 的 API 進行互操做,此類 API 支持相似功能。 默認接口成員還支持相似於「特徵」語言功能的方案。異步
默認接口成員會影響不少方案和語言元素。 請參考 C#8.0 中使用默認接口成員更新接口。async
模式匹配 提供了在相關但不一樣類型的數據中提供形狀相關功能的工具。 C# 7.0 經過使用 is
表達式和 switch
語句引入了類型模式和常量模式的語法。 這些功能表明瞭支持數據和功能分離的編程範例的初步嘗試。 隨着行業轉向更多微服務和其餘基於雲的體系結構,還須要其餘語言工具。ide
C# 8.0 擴展了此詞彙表,這樣就能夠在代碼中的更多位置使用更多模式表達式。 當數據和功能分離時,請考慮使用這些功能。 當算法依賴於對象運行時類型之外的事實時,請考慮使用模式匹配。 這些技術提供了另外一種表達設計的方式。函數
除了能夠在新位置使用新模式以外,C# 8.0 還添加了「遞歸模式」 。 任何模式表達式的結果都是一個表達式。 遞歸模式只是應用於另外一個模式表達式輸出的模式表達式。
一般狀況下,switch
語句在其每一個 case
塊中生成一個值。 藉助 Switch 表達式 ,可使用更簡潔的表達式語法。 只有些許重複的 case
和 break
關鍵字和大括號。 如下面列出彩虹顏色的枚舉爲例:
public enum Rainbow { Red, Orange, Yellow, Green, Blue, Indigo, Violet }
若是應用定義了經過 R
、G
和 B
組件構造而成的 RGBColor
類型,可以使用如下包含 switch 表達式的方法,將 Rainbow
轉換爲 RGB 值:
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)), };
這裏有幾個語法改進:
switch
關鍵字以前。 不一樣的順序使得在視覺上能夠很輕鬆地區分 switch 表達式和 switch 語句。case
和 :
元素替換爲 =>
。 它更簡潔,更直觀。default
事例替換爲 _
棄元。將其與使用經典 switch
語句的等效代碼進行對比:
public static RGBColor FromRainbowClassic(Rainbow colorBand) { switch (colorBand) { case Rainbow.Red: return new RGBColor(0xFF, 0x00, 0x00); case Rainbow.Orange: return new RGBColor(0xFF, 0x7F, 0x00); case Rainbow.Yellow: return new RGBColor(0xFF, 0xFF, 0x00); case Rainbow.Green: return new RGBColor(0x00, 0xFF, 0x00); case Rainbow.Blue: return new RGBColor(0x00, 0x00, 0xFF); case Rainbow.Indigo: return new RGBColor(0x4B, 0x00, 0x82); case Rainbow.Violet: return new RGBColor(0x94, 0x00, 0xD3); default: throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)); }; }
藉助屬性模式 ,能夠匹配所檢查的對象的屬性。 請看一個電子商務網站的示例,該網站必須根據買家地址計算銷售稅。 這種計算不是 Address
類的核心職責。 它會隨時間變化,可能比地址格式的更改更頻繁。 銷售稅的金額取決於地址的 State
屬性。 下面的方法使用屬性模式從地址和價格計算銷售稅:
public static decimal ComputeSalesTax(Address location, decimal salePrice) => location switch { { State: "WA" } => salePrice * 0.06M, { State: "MN" } => salePrice * 0.75M, { State: "MI" } => salePrice * 0.05M, // other cases removed for brevity... _ => 0M };
模式匹配爲表達此算法建立了簡潔的語法。
一些算法依賴於多個輸入。 使用元組模式,可根據表示爲元組的多個值進行切換 。 如下代碼顯示了遊戲「rock, paper, scissors(石頭剪刀布)」的切換表達式: :
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" };
消息指示獲勝者。 棄元表示平局(石頭剪刀布遊戲)的三種組合或其餘文本輸入。
某些類型包含 Deconstruct
方法,該方法將其屬性解構爲離散變量。 若是能夠訪問 Deconstruct
方法,就可使用位置模式 檢查對象的屬性並將這些屬性用於模式。 考慮如下 Point
類,其中包含用於爲 X
和 Y
建立離散變量的 Deconstruct
方法:
public class Point { public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y); }
此外,請考慮如下表示象限的各類位置的枚舉:
public enum Quadrant { Unknown, Origin, One, Two, Three, Four, OnBorder }
下面的方法使用位置模式 來提取 x
和 y
的值。 而後,它使用 when
子句來肯定該點的 Quadrant
:
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 };
當 x
或 y
爲 0(但不是二者同時爲 0)時,前一個開關中的棄元模式匹配。 Switch 表達式必需要麼生成值,要麼引起異常。 若是這些狀況都不匹配,則 switch 表達式將引起異常。若是沒有在 switch 表達式中涵蓋全部可能的狀況,編譯器將生成一個警告。
可在此模式匹配高級教程中探索模式匹配方法。
using
關鍵字的變量聲明。 它指示編譯器聲明的變量應在封閉範圍的末尾進行處理。 如下面編寫文本文件的代碼爲例:
static void WriteLinesToFile(IEnumerable<string> lines) { using var file = new System.IO.StreamWriter("WriteLines2.txt"); foreach (string line in lines) { // 若是該行不包含單詞「second」,則將該行寫入文件。 if (!line.Contains("Second")) { file.WriteLine(line); } } // 文件已在此處釋放 }
在前面的示例中,當到達方法的右括號時,將對該文件進行處理。 這是聲明 file
的範圍的末尾。 前面的代碼至關於下面使用經典 using 語句語句的代碼:
static void WriteLinesToFile(IEnumerable<string> lines) { using (var file = new System.IO.StreamWriter("WriteLines2.txt")) { foreach (string line in lines) { // 若是該行不包含單詞「second」,則將該行寫入文件。 if (!line.Contains("Second")) { file.WriteLine(line); } } } // 文件已在此處被釋放 }
在前面的示例中,當到達與 using
語句關聯的右括號時,將對該文件進行處理。
在這兩種狀況下,編譯器將生成對 Dispose()
的調用。 若是 using 語句中的表達式不可處置,編譯器將生成一個錯誤。
如今能夠向本地函數添加 static
修飾符,以確保本地函數不會從封閉範圍捕獲(引用)任何變量。 這樣作會生成 CS8421
,「靜態本地函數不能包含對 <variable> 的引用」。
考慮下列代碼。 本地函數 LocalFunction
訪問在封閉範圍(方法 M
)中聲明的變量 y
。 所以,不能用 static
修飾符來聲明 LocalFunction
:
int M() { int y; LocalFunction(); return y; void LocalFunction() => y = 0; }
下面的代碼包含一個靜態本地函數。 它能夠是靜態的,由於它不訪問封閉範圍中的任何變量:
int M() { int y = 5; int x = 7; return Add(x, y); static int Add(int left, int right) => left + right; }
ref
修飾符聲明的 struct
可能沒法實現任何接口,所以沒法實現 IDisposable。 所以,要可以處理 ref struct
,它必須有一個可訪問的 void Dispose()
方法。 這一樣適用於 readonly ref struct
聲明。
在可爲空註釋上下文中,引用類型的任何變量都被視爲不可爲空引用類型 。 若要指示一個變量可能爲 null,必須在類型名稱後面附加 ?
,以將該變量聲明爲可爲空引用類型 。
對於不可爲空引用類型,編譯器使用流分析來確保在聲明時將本地變量初始化爲非 Null 值。 字段必須在構造過程當中初始化。 若是沒有經過調用任何可用的構造函數或經過初始化表達式來設置變量,編譯器將生成警告。 此外,不能向不可爲空引用類型分配一個能夠爲 Null 的值。
不對可爲空引用類型進行檢查以確保它們沒有被賦予 Null 值或初始化爲 Null。 不過,編譯器使用流分析來確保可爲空引用類型的任何變量在被訪問或分配給不可爲空引用類型以前,都會對其 Null 性進行檢查。
能夠在可爲空引用類型的概述中瞭解該功能的更多信息。 能夠在此可爲空引用類型教程中的新應用程序中自行嘗試。 在遷移應用程序以使用可爲空引用類型教程中瞭解遷移現有代碼庫以使用可爲空引用類型的步驟。
從 C# 8.0 開始,能夠建立並以異步方式使用流。 返回異步流的方法有三個屬性:
async
修飾符聲明的。yield return
語句。使用異步流須要在枚舉流元素時在 foreach
關鍵字前面添加 await
關鍵字。 添加 await
關鍵字須要枚舉異步流的方法,以使用 async
修飾符進行聲明並返回 async
方法容許的類型。 一般這意味着返回 Task 或 Task<TResult>。 也能夠爲 ValueTask 或 ValueTask<TResult>。 方法既可使用異步流,也能夠生成異步流,這意味着它將返回 IAsyncEnumerable<T>。 下面的代碼生成一個從 0 到 19 的序列,在生成每一個數字之間等待 100 毫秒:
public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence() { for (int i = 0; i < 20; i++) { await Task.Delay(100); yield return i; } }
可使用 await foreach
語句來枚舉序列:
await foreach (var number in GenerateSequence()) { Console.WriteLine(number); }
能夠在建立和使用異步流的教程中自行嘗試異步流。
範圍和索引爲在數組中指定子範圍(Span<T> 或 ReadOnlySpan<T>)提供了簡潔語法。
此語言支持依賴於兩個新類型和兩個新運算符。
^
運算符,指定一個索引與序列末尾相關。..
),用於指定範圍的開始和末尾,就像操做數同樣。讓咱們從索引規則開始。 請考慮數組 sequence
。 0
索引與 sequence[0]
相同。 ^0
索引與 sequence[sequence.Length]
相同。 請注意,sequence[^0]
不會引起異常,就像 sequence[sequence.Length]
同樣。 對於任何數字 n
,索引 ^n
與 sequence.Length - n
相同。
範圍指定範圍的開始和末尾 。 包括此範圍的開始,但不包括此範圍的末尾,這表示此範圍包含開始但不包含末尾。 範圍 [0..^0]
表示整個範圍,就像 [0..sequence.Length]
表示整個範圍。
請看如下幾個示例。 請考慮如下數組,用其順數索引和倒數索引進行註釋:
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
可使用 ^1
索引檢索最後一個詞:
Console.WriteLine($"The last word is {words[^1]}"); // writes "dog"
如下代碼建立了一個包含單詞「quick」、「brown」和「fox」的子範圍。 它包括 words[1]
到 words[3]
。 元素 words[4]
不在此範圍內。
var quickBrownFox = words[1..4];
如下代碼使用「lazy」和「dog」建立一個子範圍。 它包括 words[^2]
和 words[^1]
。 不包括結束索引 words[^0]
:
var lazyDog = words[^2..^0];
下面的示例爲開始和/或結束建立了開放範圍:
var allWords = words[..]; // contains "The" through "dog". var firstPhrase = words[..4]; // contains "The" through "fox" var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
此外能夠將範圍聲明爲變量:
Range phrase = 1..4;
而後能夠在 [
和 ]
字符中使用該範圍:
var text = words[phrase];
可在有關索引和範圍的教程中詳細瞭解索引和範圍。