前言
微軟即將在 2024年11月12日釋出 .NET 9 的最終版本,而08月09日釋出的.NET 9 Preview 7 是最終釋出前的最後一個預覽版。這個版本將與.NET Conf 2024一同亮相,並已與 Visual Studio 2022 17.12 預覽版1一同釋出,可以直接透過Visual Studio安裝。同時Visual Studio Code 和 C# Dev Kit 擴充套件也支援.NET 9。
C# 13 作為 .NET 9 的一部分,將帶來一系列新特性,提升開發靈活性和效能,讓程式設計體驗更加流暢。儘管C# 13 尚未正式釋出,但我們可以在 .NET 9 Preview 7 中嘗試這些新特性,需要下載最新的 Visual Studio 2022 17.11 預覽版。
注意:目前 C# 13 尚未正式釋出,因此功能細節可能會有所調整。
新特性
在 C# 13 中,params關鍵字的使用已經擴充套件到不僅僅是陣列,還可以應用於任何可識別的集合型別,包括System.Span<T>、System.ReadOnlySpan<T>和實現了System.Collections.Generic.IEnumerable<T>的型別。
2、鎖物件
.NET 9 執行時引入了System.Threading.Lock型別,提供了改進的執行緒同步機制。Lock型別透過其 API 支援更高效的執行緒同步操作,例如Lock.EnterScope()方法可以進入一個獨佔作用域
3、索引器改進
索引器的使用變得更加直觀和靈活,能夠更高效地操作集合。
4、轉義序列\e
使用 \e 的好處是它可以避免與十六進位制轉義序列混淆。
5、部分屬性
部分屬性的引入使得屬性的定義和實現可以分佈在不同的檔案中,提高了程式碼的組織性和可維護性。
6、方法組自然型別改進
方法組的自然型別得到了改進,使得呼叫變得更簡單,減少了不必要的轉換。
7、ref 和 unsafe 在 async 方法和迭代器中的使用
現在 async 方法和迭代器可以使用ref變數和不安全程式碼,可以在更多情況下使用這些特性,儘管仍然有一些限制。
8、關於擴充套件型別(Extension Types)的更新
C# 13 中一個非常重大的特性,它允許向現有類新增新的方法、屬性、甚至靜態成員,而無需修改原始類程式碼。
9、LINQ 新方法
新增了CountBy和AggregateBy方法,允許按鍵聚合狀態而無需透過GroupBy分配中間分組,這為資料聚合提供了更靈活的方式
10、Foreach 支援 Index
引入了Index<TSource>(IEnumerable<TSource>),使得在 foreach 迴圈中可以快速提取可列舉項的索引
11、序列化改進
System.Text.Json在 .NET 9 中進行了改進,提供了新的選項用於 JSON 序列化,並引入了 JsonSerializerOptions.Web 單例,簡化了使用 Web 預設值進行序列化的過程。
12、效能改進
.NET 9 在異常處理、環路效能、動態 PGO(按配置檔案最佳化)、RyuJIT 編譯器以及 Arm64 指令集支援方面進行了最佳化,顯著提升了應用程式的效能。
Params 集合
params關鍵字允許方法接受一個引數列表,這個列表可以是任何實現了IEnumerable<T>介面的集合型別。
意味著可以使用方法引數來傳遞陣列、列表、元組等集合,而不必顯式地建立集合例項。
using System; using System.Collections.Generic; using System.Linq; public class Program { // 這個方法可以接受任意數量的字串引數 public static void PrintNames(params string[] names) { Console.WriteLine("Names provided:"); foreach (var name in names) { Console.WriteLine(name); } } public static void Main() { // 直接傳遞字串引數 PrintNames("Alice", "Bob", "Charlie"); // 使用陣列 string[] namesArray = new string[] { "Dave", "Eve", "Frank" }; PrintNames(namesArray); // 使用列表 List<string> namesList = new List<string> { "Grace", "Heidi", "Ivan" }; PrintNames(namesList); // 使用 LINQ 表示式 var query = from person in new List<Person> { new Person("Judy", "Walker"), new Person("Kevin", "Smith") } select person.FirstName; PrintNames(query); // 使用從集合中選擇的屬性 var persons = new List<Person> { new Person("Leonard", "Nimoy"), new Person("Morgan", "Freeman") }; PrintNames(from p in persons select p.FirstName); } } public class Person { public string FirstName { get; } public string LastName { get; } public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } }
-
直接傳遞字串字面量。
-
傳遞一個字串陣列。
-
傳遞一個字串列表。
-
使用 LINQ 查詢來傳遞查詢結果。
-
使用 LINQ 從Person物件的集合中選擇FirstName屬性。
這個示例展示了params集合的靈活性,允許以多種不同的集合型別傳遞引數,而方法內部的實現保持不變。
鎖物件
眾所周知,lock 是一種功能,透過監視器用於執行緒同步。
object lockObject = new object(); lock (lockObject) { // 關鍵區 }
但是,這個功能的開銷其實很大,會影響效能。為了解決這個問題,C# 13 實現了鎖物件。要使用此功能,只需用 System.Threading.Lock 替換被鎖定的物件即可:
using System.Threading; Lock lockObject = new Lock(); lock (lockObject) { // 關鍵區 }
這樣就可以輕鬆提高效能了。
索引器改進
對索引器的改進,其中包括在物件初始化器中使用”尾部索引"(也稱為“從末尾開始的索引”)的能力。
這種索引方式允許從集合的末尾開始計數,使用 ^ 符號來指定元素的位置。
using System; public class Demo { public static void Main() { // 定義一個可索引的型別 var data = new IndexedData { // 使用傳統的索引器初始化 Items = { [2] = "Second", [3] = "Third" }, // 使用尾部索引初始化 [^1] = "First", // 從末尾開始的第一個元素 [^2] = "Fourth" // 從末尾開始的第二個元素 }; // 列印初始化後的資料 for (int i = 0; i < data.Items.Length; i++) { Console.WriteLine($"Index {i}: {data.Items[i]}"); } } } public class IndexedData { public string[] Items { get; set; } = new string[5]; }
在這個示例中,IndexedData 類有一個名為 Items 的字串陣列屬性。
在初始化 data 物件時,我們使用了兩種索引方式:
-
傳統的索引器,透過指定索引位置(例如 [2] 和 [3])來初始化陣列元素。
-
尾部索引器,使用 ^ 符號後跟數字來指定從陣列末尾開始的位置(例如 1 和 2)。
Index 0: Index 1: Index 2: Second Index 3: Third Index 4: First
請注意,尾部索引 1 被分配給了陣列的最後一個位置(索引4),而 2被分配給了倒數第二個位置(索引3),這是因為它們是從末尾開始計數的。這種特性在初始化陣列或集合時特別有用,尤其是當你需要在已知末尾元素的情況下進行初始化時。 轉義序列 \e在 Unicode 字串中,可以使用\e 來代表 ESCAPE 字元,它等同於傳統的\u001b 或\x1b。
-
\u001b 是一個 Unicode 轉義序列,其中 \u 後跟的四位十六進位制數代表一個 Unicode 點。
-
\x1b 是一個十六進位制轉義序列,\x 後面跟的兩位十六進位制數代表一個 ASCII 字元。
-
\e 直接表示 ESCAPE 字元,它避免了可能的混淆。
部分屬性
在 C# 13 之前,屬性不支援使用partial修飾符,這意味著屬性的宣告和實現必須在同一個位置完成。這在自動生成程式碼或分離關注點時可能會帶來限制。
C# 13 改進了這一點,允許屬性跨越多個部分進行宣告和實現。特性特別適用於與原始碼生成器等工具結合使用的場景,可以更靈活地生成和管理屬性程式碼。
public class DemoModel { //宣告部分屬性 public partial int MyProperty { get; set; } } public class DemoModel { // 部分屬性的實現 public partial int MyProperty { get { return GetValue(); } set { SetValue(value); } } }
這種方式可以專注於屬性的業務邏輯部分,而將具體的實現細節留給自動化工具處理,從而提高開發效率並減少重複性編碼工作。
方法組自然型別
方法組的自然型別改進允許編譯器更精確地確定方法的自然型別,特別是在過載解析時。這意味著編譯器可以更有效地識別應該使用哪個過載版本,尤其是在涉及委託和方法組的情況下。
以下是一個示例,展示了 C# 13 中方法組自然型別的改進:
using System; public class Program { public static void Main() { // 宣告一個委託型別,它指向一個接受 Action 作為引數的方法 Action<string> action = PrintMessage; // 呼叫 PrintMessage 方法,使用方法組作為引數 action("Hello, World!"); } // 這是原始的過載版本 public static void PrintMessage(string message) { Console.WriteLine($"Original: {message}"); } // C# 13 允許更精確的自然型別推斷 public static void PrintMessage(Action<string> messagePrinter, string message) { messagePrinter(message); Console.WriteLine("Improved natural type inference in C# 13."); } }
在這個示例中,PrintMessage方法有兩個過載。第一個過載接受一個string引數,而第二個過載接受一個Action<string>和一個string引數。
在 C# 13 之前,如果嘗試使用方法組呼叫action委託,編譯器可能會在過載解析時產生模糊性,因為它需要確定使用哪個過載。
C# 13 中的方法組自然型別改進允許編譯器更準確地推斷出應該使用第一個 PrintMessage 過載,因為它更匹配傳遞的引數型別(一個字串)。第二個過載雖然也能接受字串,但它期望的是一個Action<string>型別的引數,這在方法組呼叫中是不匹配的。
請注意,這個示例僅用於說明 C# 13 中方法組自然型別改進的概念。在實際程式碼中,可能需要根據具體情況調整方法簽名和呼叫方式。
在 C# 13 之前,ref 和 unsafe 關鍵字在非同步方法(使用 async和 await 修飾的方法)和迭代器中有一些限制。
以下是一些示例,展示在 C# 13 中如何在非同步方法和迭代器中使用 ref 和 unsafe:
1、在非同步方法中使用ref
async Task RefInAsyncMethod() { int value = 0; await Task.Yield(); ref int local = ref ModifyValue(ref value); local++; // 修改原始變數的值 Console.WriteLine(value); // 輸出修改後的值 } ref int ModifyValue(ref int x) { return ref x; }
在這個示例中,ModifyValue方法返回對傳入引用的引用。在非同步方法RefInAsyncMethod中,我們使用await Task.Yield();來切換到另一個上下文,然後透過ref返回的引用來修改原始變數的值。
2、在迭代器中使用ref
IEnumerable<int> GetNumbers() { int number = 0; yield return number; // 返回第一個值 number++; // 修改狀態 yield return number; // 返回修改後的值 } // 使用迭代器 foreach (int num in GetNumbers()) { Console.WriteLine(num); }
在這個示例中,迭代器GetNumbers使用yield return來返回序列中的值。
在兩次yield呼叫之間,迭代器的狀態(number 變數)被保持,允許在第二次迭代時返回修改後的值。
3、在非同步方法中使用unsafe
async Task UnsafeInAsyncMethod() { unsafe { int* p = stackalloc int[10]; for (int i = 0; i < 10; i++) { p[i] = i; } await Task.Yield(); // 切換上下文 // 繼續使用 p for (int i = 0; i < 10; i++) { Console.WriteLine(p[i]); } } }
在這個示例中,unsafe上下文被用在非同步方法UnsafeInAsyncMethod中。我們使用stackalloc在棧上分配記憶體,並在await之前和之後訪問這個記憶體。
這展示了即使在非同步方法中,也可以執行不安全操作。
4、注意事項
-
在非同步方法中使用 ref和 unsafe需要謹慎,因為await會導致方法的執行上下文被掛起和恢復,這可能會影響對 ref 區域性變數和 unsafe 程式碼的預期行為。
-
確保在使用 ref 和 unsafe程式碼時,遵守 C# 的安全和併發規則。
C# 13 的這些改進提供了更大的靈活性,可以在非同步程式設計和迭代器中使用ref和unsafe程式碼,但同時也需要更多的注意來確保程式碼的正確性和安全性。
總結
C# 13 帶來的新特性和改進,如擴充套件型別的靈活性、params 關鍵字的增強、在非同步方法中使用ref 和unsafe的能力,以及對序列化效能的最佳化等,都極大地提升了我們開發效率,解決了很多實際開發中遇到的問題。
對 .NET 9 和 C# 13 的正式釋出充滿期待,相信將為社群帶來更加強大和便捷的工具,進一步推動技術的更新和發展。下載最新的 Visual Studio 2022-17.11 預覽版,可以親自體驗這些新特性。
下載地址
下載.NET 9.0
Visual Studio 2022 預覽版
參考連結
《C# 13: Explore the latest preview features》
《提高 C# 的生產力:C# 13 更新完全指南》
最後
如果你覺得這篇文章對你有幫助,不妨點個贊支援一下!你的支援是我繼續分享知識的動力。如果有任何疑問或需要進一步的幫助,歡迎隨時留言。也可以加入微信公眾號[DotNet技術匠] 社群,與其他熱愛技術的同行一起交流心得,共同成長!