總所周知,.NET5.0馬上就要來了,最后一個預覽版RC2也已經發布了,在11月的時候,我們就正式的發布了,然后我們就可以遷移使用了,當然今天說的重點不是.NET,今天說的是伴隨著.NET5一起到來的C#9.0,既然要了解9.0,肯定要對之前的版本也稍微了解一下,至少不會面試的時候出現:XXX這個功能用過么,哪個版本出來的知道么?一問三不知的尷尬情景,雖然官網都有,但是我相信有一小部分不看,所以還是發出來吧,
咱們就采用從小到大的順序講解吧,從6.0開始,5.0就太老了,基本不會問了,注意是C#,不是MVC,查看的都是官網的,想看更多,點擊【閱讀原文】,
C#6中新增的功能
-
get 只讀屬性
簡潔的語法來創建不可變型別,僅有get訪問器:
1 public string FirstName { get; }
2 public string LastName { get; }
當然很多時候,我們使用的是私有化來設定set,
然后通過建構式來賦值:
1 public Student(string firstName, string lastName)
2 {
3 if (IsNullOrWhiteSpace(lastName))
4 throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
5 FirstName = firstName;
6 LastName = lastName;
7 }
-
get 屬性初始化運算式
在屬性宣告中宣告自動屬性的初始值,
1 public ICollection<double> Grades { get; }
2 = new List<double>();
宣告處就可以直接被初始化,
-
Expression-bodied 函式成員
這適用于方法和只讀屬性, 例如,重寫 ToString() 通常是理想之選:
1 public override string ToString()
2 => $"{LastName}, {FirstName}";
也可以將此語法用于只讀屬性:
1 public string FullName => $"{FirstName} {LastName}";
-
using 靜態命名空間
using static 增強功能可用于匯入單個類的靜態方法, 指定要使用的類:
using static System.Math;
在 LINQ 查詢中會經常看到這種情況, 可以通過匯入 Enumerable 或 Queryable 來匯入 LINQ 模式,
-
Null 條件運算子
Null 條件運算子使 null 檢查更輕松、更流暢 , 將成員訪問 . 替換為 ?.:
1 var first = person?.FirstName;
如果person為空,回傳的值就是null,是string的默認值,如果FirstName是int型別,那回傳的就是int的默認值0,
-
$ 字串內插
新的字串內插功能可以在字串中嵌入運算式, 使用 $ 作為字串的開頭,并使用 { 和 } 之間的運算式代替序號:
1 public string GetGradePointPercentage() =>
2 $"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";
上一行代碼將 Grades.Average() 的值格式設定為具有兩位小數的浮點數,
-
when 例外篩選器
“例外篩選器”是確定何時應該應用給定的catch子句的子句 ,如果用于例外篩選器的運算式計算結果為true,則catch子句將對例外執行正常處理, 如果運算式計算結果為false,則將跳過catch子句,一種用途是檢查有關例外的資訊,以確定catch子句是否可以處理該例外:
1 public static async Task<string> MakeRequest()
2 {
3 WebRequestHandler webRequestHandler = new WebRequestHandler();
4 webRequestHandler.AllowAutoRedirect = false;
5 using (HttpClient client = new HttpClient(webRequestHandler))
6 {
7 var stringTask = client.GetStringAsync("https://docs.microsoft.com/en-us/dotnet/about/");
8 try
9 {
10 var responseText = await stringTask;
11 return responseText;
12 }
13 catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
14 {
15 return "Site Moved";
16 }
17 }
18 }
-
nameof 運算式
nameof 運算式的計算結果為符號的名稱, 每當需要變數、屬性或成員欄位的名稱時,這是讓工具正常運行的好辦法,說白了就是更好的重構:
1 if (IsNullOrWhiteSpace(lastName))
2 throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
-
Catch 和 Finally 塊中的 Await
現在可以在 catch 或 finally 運算式中使用 await, 這通常用于日志記錄方案:
1 public static async Task<string> MakeRequestAndLogFailures()
2 {
3 await logMethodEntrance();
4 var client = new System.Net.Http.HttpClient();
5 var streamTask = client.GetStringAsync("https://localHost:10000");
6 try {
7 var responseText = await streamTask;
8 return responseText;
9 } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
10 {
11 await logError("Recovered from redirect", e);
12 return "Site Moved";
13 }
14 finally
15 {
16 await logMethodExit();
17 client.Dispose();
18 }
19 }
-
索引器初始化關聯集合
可以將集合初始值設定項與 Dictionary<TKey,TValue> 集合和其他型別一起使用,在這種情況下,可訪問的 Add 方法接受多個引數, 新語法支持使用索引分配到集合中:
private Dictionary<int, string> webErrors = new Dictionary<int, string>
{
[404] = "Page not Found",
[302] = "Page moved, but left a forwarding address.",
[500] = "The web server can't come out to play today."
};
C#7.x 中新增的功能
-
out 變數
可以在方法呼叫的引數串列中宣告 out 變數,而不是撰寫單獨的宣告陳述句:
1 if (int.TryParse(input, out int result))
2 Console.WriteLine(result);
3 else
4 Console.WriteLine("Could not parse input");
為清晰明了,可能需指定 out 變數的型別,如上所示, 但是,該語言支持使用隱式型別的區域變數:
1 if (int.TryParse(input, out var answer))
2 Console.WriteLine(answer);
3 else
4 Console.WriteLine("Could not parse input");
-
Tuple 元組
低于 C# 7.0 的版本中也提供元組,但它們效率低下且不具有語言支持,這意味著元組元素只能作為 Item1 和 Item2 等參考,
可以通過為每個成員賦值來創建元組,并可選擇為元組的每個成員提供語意名稱:
1 (string Alpha, string Beta) namedLetters = ("a", "b");
2 Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");
在進行元組賦值時,還可以指定賦值右側的欄位的名稱:
1 var alphabetStart = (Alpha: "a", Beta: "b");
2 Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");
使用的時候,可以直接點出來:
1 alphabetStart.Alpha
2 alphabetStart.Beta
-
_ 棄元
C# 增添了對棄元的支持, 棄元是一個名為 _(下劃線字符)的只寫變數,可向單個變數賦予要放棄的所有值, 棄元類似于未賦值的變數;不可在代碼中使用棄元(賦值陳述句除外):
1 public class Example
2 {
3 public static void Main()
4 {
5 var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
6
7 Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
8 }
9
10 private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
11 {
12 int population1 = 0, population2 = 0;
13 double area = 0;
14
15 if (name == "New York City")
16 {
17 area = 468.48;
18 if (year1 == 1960)
19 {
20 population1 = 7781984;
21 }
22 if (year2 == 2010)
23 {
24 population2 = 8175133;
25 }
26 return (name, area, year1, population1, year2, population2);
27 }
28
29 return ("", 0, 0, 0, 0, 0);
30 }
31 }
-
is 模式匹配
模式匹配支持 is 運算式和 switch 運算式, 每個運算式都允許檢查物件及其屬性以確定該物件是否滿足所尋求的模式, 使用 when 關鍵字來指定模式的其他規則:
1 public static int SumPositiveNumbers(IEnumerable<object> sequence)
2 {
3 int sum = 0;
4 foreach (var i in sequence)
5 {
6 switch (i)
7 {
8 case 0:
9 break;
10 case IEnumerable<int> childSequence:
11 {
12 foreach(var item in childSequence)
13 sum += (item > 0) ? item : 0;
14 break;
15 }
16 case int n when n > 0:
17 sum += n;
18 break;
19 case null:
20 throw new NullReferenceException("Null found in sequence");
21 default:
22 throw new InvalidOperationException("Unrecognized type");
23 }
24 }
25 return sum;
26 }
-
case 0:是常見的常量模式, -
case IEnumerable<int> childSequence:是一種型別模式, -
case int n when n > 0:是具有附加when條件的型別模式, -
case null:是 null 模式, -
default:是常見的默認事例, -
本地函式(內部)
本地函式使你能夠在另一個方法的背景關系內宣告方法, 本地函式使得類的閱讀者更容易看到本地方法僅從宣告它的背景關系中呼叫,
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 yield return c;
17 }
18 }
注意上邊的alphabetSubsetImplementation方法,是在內部定義的,
同樣可以使用異步:
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 }
當然也支持某些使用lambda運算式來完成,
-
數字文本語法改進
C# 7.0 包括兩項新功能,可用于以最可讀的方式寫入數字來用于預期用途:二進制文本和數字分隔符 ,在創建位掩碼時,或每當數字的二進制表示形式使代碼最具可讀性時,以二進制形式寫入該數字:
1 public const int Sixteen = 0b0001_0000;
2 public const int ThirtyTwo = 0b0010_0000;
3 public const int SixtyFour = 0b0100_0000;
4 public const int OneHundredTwentyEight = 0b1000_0000;
常量開頭的 0b 表示該數字以二進制數形式寫入, 二進制數可能會很長,因此通過引入 _ 作為數字分隔符通常更易于查看位模式,如上面二進制常量所示, 數字分隔符可以出現在常量的任何位置, 對于十進制數字,通常將其用作千位分隔符:
1 public const long BillionsAndBillions = 100_000_000_000;
C#8.0中新增的功能
“.NET Core 3.x”和“.NET Standard 2.1”支持 C# 8.0;
-
Readonly 成員
可將 readonly 修飾符應用于結構的成員, 它指示該成員不會修改狀態, 這比將 readonly 修飾符應用于 struct 宣告更精細, 請考慮以下可變結構:
1 public readonly double Distance => Math.Sqrt(X * X + Y * Y);
-
默認介面方法
現在可以將成員添加到介面,并為這些成員提供實作, 借助此語言功能,API 作者可以將方法添加到以后版本的介面中,而不會破壞與該介面當前實作的源或二進制檔案兼容性, 現有的實作繼承默認實作,
1 public interface ICustomer
2 {
3 IEnumerable<IOrder> PreviousOrders { get; }
4
5 DateTime DateJoined { get; }
6 DateTime? LastOrder { get; }
7 string Name { get; }
8 IDictionary<DateTime, string> Reminders { get; }
9 }
-
Switch 運算式升級
通常情況下,switch 陳述句在其每個 case 塊中生成一個值,借助 Switch 運算式,可以使用更簡潔的運算式語法,
1 public static RGBColor FromRainbowClassic(Rainbow colorBand)
2 {
3 switch (colorBand)
4 {
5 case Rainbow.Red:
6 return new RGBColor(0xFF, 0x00, 0x00);
7 case Rainbow.Orange:
8 return new RGBColor(0xFF, 0x7F, 0x00);
9 case Rainbow.Yellow:
10 return new RGBColor(0xFF, 0xFF, 0x00);
11 case Rainbow.Green:
12 return new RGBColor(0x00, 0xFF, 0x00);
13 case Rainbow.Blue:
14 return new RGBColor(0x00, 0x00, 0xFF);
15 case Rainbow.Indigo:
16 return new RGBColor(0x4B, 0x00, 0x82);
17 case Rainbow.Violet:
18 return new RGBColor(0x94, 0x00, 0xD3);
19 default:
20 throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
21 };
22 }
這里有幾個語法改進:
-
變數位于
switch關鍵字之前, 不同的順序使得在視覺上可以很輕松地區分 switch 運算式和 switch 陳述句, -
將
case和:元素替換為=>, 它更簡潔,更直觀, -
將
default事例替換為_棄元, -
正文是運算式,不是陳述句,
1 public static RGBColor FromRainbow(Rainbow colorBand) =>
2 colorBand switch
3 {
4 Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
5 Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
6 Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
7 Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
8 Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
9 Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
10 Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
11 _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
12 };
-
屬性模式
借助屬性模式,可以匹配所檢查的物件的屬性, 請看一個電子商務網站的示例,該網站必須根據買家地址計算銷售稅, 這種計算不是 Address 類的核心職責, 它會隨時間變化,可能比地址格式的更改更頻繁, 銷售稅的金額取決于地址的 State 屬性, 下面的方法使用屬性模式從地址和價格計算銷售稅:
1 public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
2 location switch
3 {
4 { State: "WA" } => salePrice * 0.06M,
5 { State: "MN" } => salePrice * 0.075M,
6 { State: "MI" } => salePrice * 0.05M,
7 // other cases removed for brevity...
8 _ => 0M
9 };
在 LINQ 查詢中會經常看到這種情況, 可以通過匯入 Enumerable 或 Queryable 來匯入 LINQ 模式,
-
元組模式
一些演算法依賴于多個輸入, 使用元組模式,可根據表示為元組的多個值進行切換, 以下代碼顯示了游戲“rock, paper, scissors(石頭剪刀布)”的切換運算式::
1 public static string RockPaperScissors(string first, string second)
2 => (first, second) switch
3 {
4 ("rock", "paper") => "rock is covered by paper. Paper wins.",
5 ("rock", "scissors") => "rock breaks scissors. Rock wins.",
6 ("paper", "rock") => "paper covers rock. Paper wins.",
7 ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
8 ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
9 ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
10 (_, _) => "tie"
11 };
如果person為空,回傳的值就是null,是string的默認值,如果FirstName是int型別,那回傳的就是int的默認值0,
-
using 宣告
using 宣告是前面帶 using 關鍵字的變數宣告, 它指示編譯器宣告的變數應在封閉范圍的末尾進行處理, 以下面撰寫文本檔案的代碼為例:
1 static int WriteLinesToFile(IEnumerable<string> lines)
2 {
3 using var file = new System.IO.StreamWriter("WriteLines2.txt");
4 // Notice how we declare skippedLines after the using statement.
5 int skippedLines = 0;
6 foreach (string line in lines)
7 {
8 if (!line.Contains("Second"))
9 {
10 file.WriteLine(line);
11 }
12 else
13 {
14 skippedLines++;
15 }
16 }
17 // Notice how skippedLines is in scope here.
18 return skippedLines;
19 // file is disposed here
20 }
前面的代碼相當于下面使用經典 using 陳述句的代碼:
static int WriteLinesToFile(IEnumerable<string> lines)
{
// We must declare the variable outside of the using block
// so that it is in scope to be returned.
int skippedLines = 0;
using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
{
foreach (string line in lines)
{
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
else
{
skippedLines++;
}
}
return skippedLines;
} // file is disposed here
}
-
Static 靜態本地函式
現在可以向本地函式添加 static 修飾符,以確保本地函式不會從封閉范圍捕獲(參考)任何變數,
1 int M()
2 {
3 int y = 5;
4 int x = 7;
5 return Add(x, y);
6 static int Add(int left, int right) => left + right;
7 }
-
async 異步流
從 C# 8.0 開始,可以創建并以異步方式使用流,回傳異步流的方法有三個屬性:
-
它是用
async修飾符宣告的, -
它將回傳 IAsyncEnumerable<T>,
-
該方法包含用于在異步流中回傳連續元素的
yield return陳述句,
1 public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()
2 {
3 for (int i = 0; i < 20; i++)
4 {
5 await Task.Delay(100);
6 yield return i;
7 }
8 }
9
10 await foreach (var number in GenerateSequence())
11 {
12 Console.WriteLine(number);
13 }
異步可釋放:
從 C# 8.0 開始,語言支持實作 System.IAsyncDisposable 介面的異步可釋放型別,可使用 await using 陳述句來處理異步可釋放物件,
-
索引和范圍
范圍指定范圍的開始和末尾 , 包括此范圍的開始,但不包括此范圍的末尾,這表示此范圍包含開始但不包含末尾 , 范圍 [0..^0] 表示整個范圍,就像 [0..sequence.Length] 表示整個范圍,
請看以下幾個示例, 請考慮以下陣列,用其順數索引和倒數索引進行注釋:
1 var words = new string[]
2 {
3 // index from start index from end
4 "The", // 0 ^9
5 "quick", // 1 ^8
6 "brown", // 2 ^7
7 "fox", // 3 ^6
8 "jumped", // 4 ^5
9 "over", // 5 ^4
10 "the", // 6 ^3
11 "lazy", // 7 ^2
12 "dog" // 8 ^1
13 }; // 9 (or words.Length) ^0
14
15 // 可以使用 ^1 索引檢索最后一個詞:
16 Console.WriteLine($"The last word is {words[^1]}");
17 // writes "dog"
以下代碼創建了一個包含單詞“quick”、“brown”和“fox”的子范圍, 它包括 words[1] 到 words[3], 元素 words[4] 不在該范圍內,
1 var quickBrownFox = words[1..4];
2
3 var allWords = words[..]; // contains "The" through "dog".
4 var firstPhrase = words[..4]; // contains "The" through "fox"
5 var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
-
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);
Console.WriteLine(string.Join(" ", numbers)); // output: 17 17
Console.WriteLine(i); // output: 17
C#9.0 中新增的功能
.NET5支持C#9.0.
-
記錄型別
C# 9.0 引入了記錄型別,這是一種參考型別,它提供合成方法來提供值語意,從而實作相等性, 默認情況下,記錄是不可變的,
1 public record Person
2 {
3 public string LastName { get; }
4 public string FirstName { get; }
5
6 public Person(string first, string last)
7 => (FirstName, LastName) = (first, last);
8 }
-
Init 僅限的資源庫
從 C# 9.0 開始,可為屬性和索引器創建 init 訪問器,而不是 set 訪問器, 呼叫方可使用屬性初始化運算式語法在創建運算式中設定這些值,但構造完成后,這些屬性將變為只讀, 僅限 init 的資源庫提供了一個視窗用來更改狀態,
1 public struct WeatherObservation
2 {
3 public DateTime RecordedAt { get; init; }
4 public decimal TemperatureInCelsius { get; init; }
5 public decimal PressureInMillibars { get; init; }
6
7 public override string ToString() =>
8 $"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
9 $"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
10 }
呼叫方可使用屬性初始化運算式語法來設定值,同時仍保留不變性:
1 var now = new WeatherObservation
2 {
3 RecordedAt = DateTime.Now,
4 TemperatureInCelsius = 20,
5 PressureInMillibars = 998.0m
6 };
-
頂級陳述句
頂級陳述句從許多應用程式中洗掉了不必要的流程,只有一行代碼執行所有操作, 借助頂級陳述句,可使用 using 陳述句和執行操作的一行替換所有樣本:
1 using System;
2
3 Console.WriteLine("Hello World!");
如果需要單行程式,可洗掉 using 指令,并使用完全限定的型別名稱:
1 System.Console.WriteLine("Hello World!");
-
模式匹配增強功能
C# 9 包括新的模式匹配改進:
-
型別模式要求在變數是一種型別時匹配
-
帶圓括號的模式強制或強調模式組合的優先級
-
聯合
and模式要求兩個模式都匹配 -
析取
or模式要求任一模式匹配 -
求反
not模式要求模式不匹配 -
關系模式要求輸入小于、大于、小于等于或大于等于給定常數,
1 public static bool IsLetter(this char c) =>
2 c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
3
4 public static bool IsLetterOrSeparator(this char c) =>
5 c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';
-
除錯和完成功能
在 C# 9.0 中,已知創建物件的型別時,可在 new 運算式中省略該型別, 最常見的用法是在欄位宣告中:
1 private List<WeatherObservation> _observations = new();
當需要創建新物件作為引數傳遞給方法時,也可使用目標型別 new, 請考慮使用以下簽名的 ForecastFor() 方法:
1 public WeatherForecast ForecastFor(DateTime forecastDate, WeatherForecastOptions options)
可按如下所示呼叫該方法:
1 var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());
好啦,關于c#的更新呢,暫時就這么多了,看著很長,其實很多咱們平時都已經使用到了,當然還有一些不太常用的我沒有去列舉,更多的內容,左下角點擊【閱讀原文】吧,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/175685.html
標籤:.NET技术
下一篇:上傳圖片到阿里云Oss不全
