歷史回顧 - C# 12 新功能盤點
| | | 1 | |
參考資料:微軟官方文件 C# 12
C# 12 隨 .NET 8 SDK 在 2023 釋出。
- Primary Constructors
在 class/struct 宣告直接寫主建構式帶入參數,用來初始化欄位/屬性、轉傳給 base(...)、或在成員中引用,其他建構子需以 this(...) 轉呼叫主建構式。public readonly struct Distance(double dx, double dy) { public readonly double Magnitude { get; } = Math.Sqrt(dx * dx + dy * dy); public readonly double Direction { get; } = Math.Atan2(dy, dx); } // 背後會轉換成以下程式 public readonly struct Distance { public readonly double Magnitude { get; } public readonly double Direction { get; } public Distance(double dx, double dy) { Magnitude = Math.Sqrt(dx * dx + dy * dy); Direction = Math.Atan2(dy, dx); } } // 另一個範例 public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary) { public string FirstName { get; } = firstName; public string LastName { get; } = lastName; public DateTime HireDate { get; } = hireDate; public decimal Salary { get; } = salary; public override string ToString() => $"{FirstName} {LastName} hired on {HireDate:d} with {Salary:C}"; } - 集合表示式
以中括號[ <元素>,<元素>,... ]直接建立可指派到多種集合型別的集合值,並支援展開運算子..合併集合,可用於變數初始化、屬性、參數等位置,但不能用在常數或方法參數的預設值。// 指派到多種集合型別 List<int> list = [1, 2, 3]; int[] arr = [4, 5, 6]; Span<string> days = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; // 展開合併 string[] vowels = ["a","e","i","o","u"]; string[] consonants = ["b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","z"]; string[] alphabet = [..vowels, ..consonants, "y"]; - Inline 陣列
[System.Runtime.CompilerServices.InlineArray(n)] 可在 struct 宣告固定長度的內嵌陣列,優點是可確保記憶體配置連續、降低堆積配置與 GC 壓力,適用高效能需求場景。using System.Runtime.CompilerServices; [InlineArray(8)] public struct ByteBuffer { private byte _element0; // 編譯器生成連續元素儲存 } void Use() { ByteBuffer buf = default; for (int i = 0; i < 8; i++) buf[i] = (byte)i; // 可用索引器 } - Lambda 參數可省略、支援預設值
不解釋,之前老覺得 Lambda 函式不能省略參數,無法給預設值很遜,C# 12 起封印解除。// 帶預設值的 lambda var greet = (string name = "World", string prefix = "Hello") => $"{prefix}, {name}!"; Console.WriteLine(greet()); // Hello, World! Console.WriteLine(greet("C# 12")); // Hello, C# 12! Console.WriteLine(greet(prefix: "Hi")); // Hi, World! - ref readonly 參數
允許以參照傳遞參數但保證不修改其值,也不允許更換參照指向對象,能兼顧大型 struct 的效能與語意安全;對已使用 ref 但實際未修改的 API,可改成 ref readonly 而不破壞呼叫端相容性。public readonly struct BigStruct(int a, int b) { public int A => a; public int B => b; } static int Sum(ref readonly BigStruct s) { // s = new BigStruct(1,2); // 編譯錯誤:不可重新指派 return s.A + s.B; } var bs = new BigStruct(10, 20); int r = Sum(ref bs); - using 別名可應用於所有型別
using 別名現在可對任何型別(含 tuple、陣列、指標、封閉泛型),用以縮短冗長型別、釐清語意、或消除命名衝突;限制是不支援 nullable 參考型別。using Pair = (int A, int B); using Map = System.Collections.Generic.Dictionary<string, object>; using Series = System.Collections.Generic.List<(DateTime T, double V)>; using S = string?; // 不允許:不支援 nullable 參考型別 Pair p = (1, 2); Map m = new() { ["Name"] = "Alice", ["Age"] = 25 }; Series s = []; - Experimental Attribute
[System.Diagnostics.CodeAnalysis.Experimental] 可標示實驗性 API,使用端會收到診斷(預設為警告,但比照錯誤將導致建置失敗),需明確抑制或設定才能使用,可用於套件/程式庫逐步釋出預覽功能。(註:與 Obsolete 相反:Obsolete 提醒淘汰,Experimental 提醒尚不穩定)using System.Diagnostics.CodeAnalysis; [Experimental("EXP001")] public class ExperimentalService { public void DoWork() { } } // 使用端會出現實驗性 API 診斷,需以 pragma 或編譯選項抑制 #pragma warning disable EXP001 new ExperimentalService().DoWork(); #pragma warning restore EXP001 - Interceptors (預覽階段)
Interceptors 仍為預覽功能,用於在編譯器/工具鏈層級重新導向呼叫以插入邏輯(如記錄/分析/改寫),尚未穩定,實務需留意版本相容與特性啟用條件。
A concise overview of C# 12’s key features.
Comments
# by ChrisTorng
看到 public readonly double Magnitude { get; } = Math.Sqrt(dx * dx + dy * dy); 想到最近踩坑經驗來分享。 此例的值是不變 (建構式給值) 因此適用。 若值有需要變動,可寫為: public double Magnitude => Math.Sqrt(dx * dx + dy * dy); 就是每次呼叫屬性皆執行 Sqrt()。 我踩的坑就是寫了 { get; } = ,沒想到它不會每次執行,而是建構式執行給值一次。