參考資料:微軟官方文件 C# 11

C# 11 對映 .NET 7,每年固定出新版後,版本間不再有躍進式的大變革,風格上較像持續優化,語法糖的比例增加。

  1. Raw String Literal 原始字串常值
    C# 也能像 Python 一樣用 """ 輸入多行文字,不像 @"..." 遇雙引號要改 "",並支援縮排,非常方便好用,大推。( 去年發現驚喜之餘,還寫了文章分享:C# 也可以用 """ 內嵌多行字串)
    另外,字串插值中用 包含的內容允許換行,書寫上更彈性,可讀性也大大提高。
     var data = new[] {1, 2, 3};
     string s = $"""
     Result:
     {
         string.Join(", ",
             data
                 .Select(x => x * x)
                 .Where(x => x > 1)
         )
     }
     """;   
    
  2. Generic Math Support 泛型數學運算支援
    透過 System.Numerics INumber<T> 介面INumberBase<TSelf>,泛型方法能一次涵蓋 int、double、decimal 等數字型別,以便使用 +、-、*、/ 等運算子與常用數學 API。
    using System.Numerics;
    
    static T Add<T>(T x, T y) where T : INumber<T>
        => x + y;
    
    static T Clamp01<T>(T v) where T : INumber<T>
    {
        if (v < T.Zero) return T.Zero; // 來自 INumberBase<TSelf> 介面,代表 0
        if (v > T.One)  return T.One;
        return v;
    }
    
    // 用於多種數值型別
    var a = Add(3, 7);           // int -> 10
    var b = Add(1.2, 2.3);       // double -> 3.5
    var c = Clamp01(1.5m);       // decimal -> 1   
    
  3. Generic Attribute
    Attribute 本身可為泛型,允許以型別參數攜帶型別資訊,省下 typeof 傳遞寫法與重複樣板(如下例中的 ParamType 屬性)。
    // C# 10 做法
    public class TypeAttribute : Attribute
    {
       public TypeAttribute(Type t) => ParamType = t;
    
       public Type ParamType { get; }
    }    
    // 註:用 Attribute 記錄型別資訊,供 Reflection 或 Code Generator 參考
    [Type(typeof(string))]
    public string Method() => default;
    
    // C# 11 
    public class GenericAttribute<T> : Attribute { }
    [Generic<string>()]
    public string Method() => default;
    
  4. UTF-8 String Literals / UTF-8 字串常值
    "u8 後綴可建立 ReadOnlySpan 的 UTF-8 常值,避免執行期 Encoding.UTF8.GetBytes() 轉換(註:.NET 內部是用 UTF-16 保存字串)與記憶體配置,對高效能需求的 I/O 與序列化很實用,當需要頻繁使用 UTF-8 位元組溝通的網路與檔案情境可減少 GC 壓力。
    ReadOnlySpan<byte> hello = "hello"u8;
    // 範例:寫入 UTF-8 bytes
    using var stream = File.Create("greet.txt");
    stream.Write(hello);
    
  5. switch 支援更多陣列清單比對的花式寫法
    static string Describe(int[] xs) => xs switch
    {
        []                => "empty",
        [var a]           => $"one: {a}",
        [0, .. var rest]  => $"starts with 0, rest len={rest.Length}",
        [> 10, > 10, .. ] => "first two > 10",
        [.., 99]          => "ends with 99",
        _                 => "other"
    };
    
  6. File-Local Types
    以 file 修飾字限定類別僅在同一原始檔可見,以 internal 更嚴格,避免與其他檔案或生成的程式碼撞名,寫程式碼產生器時特別有用。
    file class LineTokenizer
    {
        public static string[] Split(string s) => s.Split(' ', StringSplitOptions.RemoveEmptyEntries);
    }
    
  7. Required Members
    在屬性或欄位前加上 required,強制呼叫端於物件初始化期間賦值;可用 SetsRequiredMembers 屬性標註建構式,宣告該建構式內已完成所有 required 成員初始化。
    required 成員可見範圍不可低於建構式、不可與 ref、ref readonly、const、static、fixed 等修飾詞併用,通常會搭配 init 以維持不可變初始化語意。
    using System.Diagnostics.CodeAnalysis;
    
    public class Person
    {
        public required string FirstName { get; init; }
        public required string LastName  { get; init; }
        public int? Age { get; set; }
    
        [SetsRequiredMembers]
        public Person(string first, string last) => (FirstName, LastName) = (first, last);
    }
    
    // 物件初始化器滿足 required
    var p1 = new Person("Ada", "Lovelace");
    
    // 使用物件初始化器指定 required
    var p2 = new Person { FirstName = "Alan", LastName = "Turing", Age = 41 };
    
  8. Struct 自動指定預設值
    新版編譯器保證 struct 建構式執行時所有欄位皆會被設為其型別的預設值,未顯式指定者自動設成 default,降低以往「未完全指定欄位」導致的編譯錯誤。
    public struct Counter
    {
        public int Value;
        public Counter() { /* 建構式不需指定 Value,在 C# 11 不會報錯 */ }
    }
    
    var c = new Counter();
    Console.WriteLine(c.Value); // 0
    
  9. Span<char>ReadOnlySpan<char> 可與常數字串進行 is 比較
    ReadOnlySpan<char> s = "hello".AsSpan();
    if (s is "hello")
    {
        Console.WriteLine("match!");
    }    
    
  10. nameof 可用於 Attribute
    在方法的 Attribute 可用 nameof 解析方法的型別參數與參數名稱,在 Nullable 註解、驗證設定相關應用上很方便。
    public class NotNullAttribute : Attribute
    {
        public NotNullAttribute(string paramName) {}
    }
    
    public class Demo
    {
        [return: NotNull(nameof(value))]
        public string Echo(string value) => value;
    }
    
  11. Numeric IntPtr
    IntPtr 現在支援「數值」型別語意,可用於算術與常見轉換,使處理平台相依指標大小時更自然。
    nint a = 10;       // nint 為 IntPtr 的別名語法糖
    nint b = a + 5;    // 可進行算術運算
    IntPtr p = b;      // 可與 IntPtr 互轉
    
  12. ref fields 與 scoped ref
    允許在型別內宣告 ref 欄位,用於高效封裝對外部記憶體的參考;搭配 scoped 修飾可限制 ref 的存活範圍以避免逃逸(編譯器禁止傳到範圍外被較長生命週期的容器儲存),讓低層次效能最佳化更安全。
    public ref struct BufferWrapper
    {
        private ref int _head; // ref 欄位 (實務上多用在大型物件較有效益)
        public BufferWrapper(ref int head) { _head = ref head; }
    
        public static scoped ref int First(scoped Span<int> span)
            => ref span; // scoped ref 防止逃逸
    }
    
  13. 提高委派時推導適用重載方法的精準度
    void Log(string s) => Console.WriteLine(s);
    void Log(object o) => Console.WriteLine(o);
    
    Action<string> a = Log;   // 更容易選到 (string) 多載
    a("hello");    
    
  14. Warning Wave 7
    C# 編譯器的每個版本中可能會引進新的警告和錯誤,當現有程式碼可以報告新的警告時,這些警告會在稱為警告波(Warning Wave,分批啟用的概念),例如:C# 9 新增 Warning Wave 5、C# 10 新增 Warning Wave 6。 這些「可選擇啟用」的新警告用來在不破壞既有程式的前提下推廣更嚴格或更未來相容的診斷。 Warning Wave 7 啟用更多靜態分析診斷,以提早發現潛在問題(例如 Nullable、模式比對、語意陷阱),建議在 CI 開啟相應波次以提高程式品質。

A concise overview of C# 11’s key features.


Comments

# by ChrisTorng

想說我會用簡化語法,刪除可不附加的 Attribute: [Type(typeof(string))] public string Method() => default; [Generic<string>()] public string Method() => default;

# by Jeffrey

to ChrisTorng, YES! 省略名稱的 Attribute 後綴較符合慣例。感謝提醒。

Post a comment