1、Readonly成員 可將readonly修飾符應用於結構的任何成員,它指示該成員不會修改狀態。這比將readonly修飾符應用於struct聲明更精細。 像大多數結構一樣ToString()方法不會修改狀態。可以通過readonly修飾符添加到ToString()的聲明來對此進行指示: 上述更 ...
1、Readonly成員
可將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,如下圖所示:
需要創建防禦性副本時,編譯器會發出警告,Distance屬性不會更改狀態,因此可以通過將readonly修飾符添加到聲明來修複此警告:
public readonly double Distance => Math.Sqrt(X * X + Y * Y);
請註意,readonly修飾符對於只讀屬性是必須的。編譯器不會假設get訪問器不修改狀態;必須明確聲明readonly,編譯器會強制實施以下規則:
readonly成員不修改狀態,除非readonly修飾符,否則不會編譯以下方法:
public readonly void Translate(int xOffset,int yOffset) { X += xOffset; Y += yOffset; }
通過此功能,可以指定設計意圖,使編譯器可以強制執行該意圖,並基於該意圖進行優化。
2、預設介面成員
現在可以將成員添加到介面,並未這些成員提供實現。藉助次語言功能,API坐著可以將方法添加到以後版本的介面中,而不會破壞與該介面當前實現的源或耳機中南海文件相容性。現在有的實現繼承預設介面。此功能使C#與面向Android 或Swift的API進行互操作,此類API支持類似功能。預設介面成員還支持類似於“特征”語言功能的方案。
預設介面成員會影響很多方案和語言元素。https://docs.microsoft.com/zh-cn/dotnet/csharp/tutorials/default-interface-methods-versions
3、在更多位置中使用更多模式
模式匹配提供了在相關但不同類型的數據中提供形狀相關功能的工具。C#7.0通過使用is表達式和switch語句引入了類型模式和常量模式的語法。這些功能代表了支持數據和功能分離時的編程範例的初步嘗試。隨著行業轉向更多微服務和其他基於雲的體繫結構,還需要其他語言功能。
C#8.0擴展了此辭彙表。這樣就可以在代碼中的更多位置使用更多模式表達式。當數據和功能分離時,請考慮使用這些功能。當演算法依賴於對象運行時類型以外的事實時,請考慮使用模式匹配。這些技術提供了另一種表達設計的方式。
除了烤魚在新位置使用新模式之外,C#8.0還添加了“遞歸模式”。任何模式表達式的結果都是一個表達式。遞歸模式只是應用於另外一個模式表達式輸出的模式表達式。
switch表達式
通常情況下,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)), };
這裡有幾個語法改進:
1、變數位於switch關鍵字之前。不同的順序使得在視覺上可以很輕鬆的區分switch表達式和switch語句。
2、將case和:元素替換為=>,它更簡潔,更直觀。
3、將default事例替換為_棄元。
4、正文是表達式,不是語句。
將其與使用經典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,pack,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; set; } public int Y { get; set; } 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表達式中涵蓋所有可能的情況,編譯器將生成一個警告。
可以在模式匹配高級教程中探索模式匹配方法。https://docs.microsoft.com/zh-cn/dotnet/csharp/tutorials/pattern-matching
4、using聲明
using聲明是前面帶using關鍵字的變數聲明。它指示編譯器聲明的變數應該在封閉範圍的末尾進行處理。以下麵編寫文本文件的代碼為例:
static void WriteLinesToFile(IEnumerable<string> lines) { using var file = new System.IO.StreamWriter("WriteLines2.txt"); foreach (var 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 (var line in lines) { //如果該行不包含單詞“Second”,則將該行寫入文件 if (!line.Contains("Second")) { file.WriteLine(line); } } }//文件已在此處被釋放 }
在前面的示例中,當到達與using語句關聯的右括弧時,將對該文件進行處理。在這兩種情況下,編譯器將生成對Dispose()的調用。如果using語句中的表達式不可處置,編譯器將生成一個錯誤。
5、靜態本地函數
現在可以向本地函數添加static修飾符,以確保本地函數不會從封閉範圍捕獲(引用)任何變數。這樣做會生成CS8421,“靜態本地函數不能包含對<variable>的引用”。
考慮下列代碼,本地函數LocalFunction訪問在封閉範圍(方法M)中聲明的變數y,因此,不能用static修飾符來聲明LocalFunction:
int M() { int y; LocalFunction(); return y; void LocalFunction() => y = 0; }
如果在LocalFunction方法前面加上static:
下麵的代碼包含一個靜態本地函數,它可以是靜態的,因為它不訪問封閉範圍中的熱呢變數:
int M() { int y = 5; int x = 7; return Add(x, y); static int Add(int left, int right) => left + right; }
6、可處置的ref結構
用ref修飾符聲明的struct可能無法實現任何藉口,因此無法實現IDisposable。因此,要能夠處理ref struct,它必須有一個可訪問的void Dispose()方法。這同樣適用於readonly ref struct聲明。
7、可謂空引用類型
在可謂空註釋上下文中,引用類型的任何變數都被視為不可謂空引用類型。若要指示一個變數可能為null,必須在類型名稱後面附加?,以將該變數聲明為可謂空引用類型。
如果,不用?來聲明,傳統代碼中,這樣寫,會報錯:
static void Main(string[] args) { List<string> list = null; var newList = list.Where(s => s.Length > 2) ?? new List<string>(); foreach (var item in newList) { Console.WriteLine(item); } Console.WriteLine("ok"); Console.ReadLine(); }
改進之後,就沒問題了:
static void Main(string[] args) { List<string> list = null; var newList = list?.Where(s => s.Length > 2) ?? new List<string>(); foreach (var item in newList) { Console.WriteLine(item); } Console.WriteLine("ok"); Console.ReadLine(); }
對於不可為空引用類型,編譯器使用流分析來確保在聲明時將本地變數初始化為非Null值,欄位必須在構造過程中初始化,如果沒有通過調用任何可用的構造函數或通過初始化表達式來設置變數,編譯器將生成警告。此外,不能向不可為空引用類型分配一個可以為Null的值。
不對可為空引用類型進行檢查以確保他們沒有被賦予Null值或初始化為Null。不過,編譯器使用流分析來確保可為空引用類型的任何變數在被訪問或分配給不可為空引用類型之前,都會對其Null性進行檢查。
8、非同步流
從C#8.0開始,可以創建並以非同步方式使用流。返回非同步流的方法有三個屬性:
1、它是用async修飾符聲明的
2、它將返回IAsyncEnumerable<T>
3、該方法包含用於在非同步流中返回連續元素的yield return 語句
使用非同步流需要在枚舉流元素時在foreach關鍵字前面添加await關鍵字。談價await關鍵字需要枚舉非同步流的方法,以使用async修飾符進行聲明並返回async方法允許的類型。通常在意味著返回Task或Task<TResult>。也可以為ValueTask或ValueTask<TResult>。方法既可以使用非同步流,也可以生成非同步流,這意味著它將返回IAsyncEnumerable<T>。下麵的代碼生成一個從0到10的序列,在生成沒個數字之間等待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語句來枚舉序列:
public static async void TestGenerateSequence() { await foreach (var item in GenerateSequence()) { Console.WriteLine(item); } }
9、索引和範圍
範圍和索引為在數組中指定資範圍(Span<T>或ReadOnlySpan<T>)提供了簡潔語法。
此語言支持依賴於兩個新類型和兩個新運算符。
1、System.Index表示一個序列索引
2、^運算符,指定一個索引與序列末尾相關。
3、System.Range表示序列的子範圍
4、範圍運算符(..),用於指定範圍的開始和末尾,就像操作數一樣。
讓我們從索引規則開始。請考慮數組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]}");
以下代碼創建了一個包含單詞"quick"、"brown"和"fox"的子範圍。它包括words[1]到words[3],元素words[4]不在此範圍內。
var quickBrownFox = words[1..4]; foreach (var item in quickBrownFox) { Console.WriteLine(item); }
以下代碼使用"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];