.NET9 - 新功能體驗(二)

IT规划师發表於2024-11-22

書接上回,我們繼續來聊聊.NET9和C#13帶來的新變化。

01、新的泛型約束 allows ref struct

這是在 C# 13 中,引入的一項新的泛型約束功能,允許對泛型型別引數應用 ref struct 約束。

可能這樣說不夠直觀,簡單來說就是Span、ReadOnlySpan型別,我們直接看下面的程式碼示例:

在沒有新的約束allows ref struct之前,Span是不能當引數傳入的,直接編譯錯誤,但是有了新約束則就可以支援Span引數了。

因此C# 13 中引入了 where T : allows ref struct 泛型約束後使得我們可以對泛型引數型別進行更加精細的控制。透過這個特性,泛型方法或類就可以接受 ref struct 型別,如 Span 、ReadOnlySpan等,因為這些型別是在棧上分配記憶體,能夠提供更高效的記憶體管理和更快的執行速度,所以這個新特性特別適用於高效能、記憶體密集型的泛型方法和類,可以有效避免堆分配和垃圾回收的開銷。

02、ref struct介面

在 C# 13 之前,ref struct 是無法實現介面的。 從 C# 13 開始,ref struct可實現介面,但必須遵循 ref 安全性規則。 例如,由於需要裝箱轉換,因此無法將 ref struct 型別轉換為介面型別。

如上圖,ref struct型別可以實現IInterface介面,但是當用IInterface介面去接收RefStructInterface型別時則直接編譯報錯,無論直接接收還是強制轉換都是不支援的。

03、在非同步方法中使用ref struct

從C# 13開始,ref struct可以在非同步方法中使用,但是有一個限制:它們不能在與 await 表示式同一個程式碼塊中互動。這是為了避免 ref struct在跨越非同步操作時引發記憶體安全問題,因為 ref struct 型別的例項通常儲存在棧上,並且不能在非同步操作中跨越棧幀。

下面程式碼是在非同步方法中使用ref struct示例:

ref int Process(ref int x)
{
    return ref x;
}
//在非同步方法中使用ref
async Task RefInAsync()
{
    var value = 0;
    await Task.Delay(0);
    ref var local = ref Process(ref value);
}

04、在迭代器中使用ref struct

從 C# 13 開始,允許在迭代器方法中使用 ref struct,前提是滿足以下條件:不能在包含 yield return 的程式碼段中使用它們。這是因為yield return 語句會導致方法的執行暫停並在以後繼續執行。如果在這期間使用了ref struct,可能會導致這些型別的生命週期管理出現問題(例如跨越棧幀的切換)。為了避免這種問題,C# 13 規定,如果要在迭代器方法中使用 ref struct,則不能在 yield return 語句所在的程式碼段中操作它們。

下面是在迭代器中使用ref struct示例程式碼:

ref int Process(ref int x)
{
    return ref x;
}
//在迭代器中使用ref
IEnumerable<int> RefInIterator(int[] array)
{
    for (var i = 0; i < array.Length; i++)
    {
        ref var v = ref Process(ref array[i]);
        yield return v;
    }
}

05、部分屬性、部分索引器

早在C#2就引入了部分類,在C#3引入了部分方法,到現在C#13又新增了部分屬性和部分索引器。

這一改進這意味著允許屬性和索引器可以跨越多個部分進行宣告和實現。這給自動生成程式碼或分離關注點帶來了極大便利,也更加靈活地生成和管理屬性程式碼,特別適用於與原始碼生成器等工具結合使用的場景。

以下是 C# 13 中屬性支援partial的示例:

public partial class PartialExamples
{
    //部分屬性
    public partial int Capacity { get; set; }
    //部分索引器
    public partial string this[int index] { get; set; }
    //部分方法
    public partial string? TryGetItemAt(int index);
}
public partial class PartialExamples
{
    private List<string> _items = ["one", "two", "three", "four", "five"];
    //部分屬性
    public partial int Capacity
    {
        get => _items.Count;
        set
        {
            if ((value != _items.Count) && (value >= 0))
            {
                _items.Capacity = value;
            }
        }
    }
    //部分索引器
    public partial string this[int index]
    {
        get => _items[index];
        set => _items[index] = value;
    }
    //部分方法
    public partial string? TryGetItemAt(int index)
    {
        if (index < _items.Count)
        {
            return _items[index];
        }
        return null;
    }
}

06、foreach 支援Index

相信很多人都遇到過想要在foreach的時候獲取集合元素當前索引,一般兩種選擇,一種自己維護一個變數,一種直接改用for。

而.NET9開始總算改變了這一現狀,可以在foreach時候同時獲取到當前元素及其索引。

我們下面看看Index()方法給我們帶來了多大便利,程式碼如下:

//.NET 9 之前
 public void Loop()
 {
     List<string> items = ["張三", "李四", "王五"];
     var idx = 0;
     foreach (var item in items)
     {
         idx++;
         Console.WriteLine($"第{idx}個人名字是:{item}");
     }
 }
 //.NET 9
 public void LoopNew()
 {
     List<string> items = ["張三", "李四", "王五"];
     //直接獲取索引、元素
     foreach ((int Index, string Item) in items.Index())
     {
         Console.WriteLine($"第{Index + 1}個人名字是:{Item}");
     }
 }
 //.NET 9
 public void LoopNew2()
 {
     List<string> items = ["張三", "李四", "王五"];
     //先獲取元組後,再獲取索引、元素
     foreach (var item in items.Index())
     {
         Console.WriteLine($"第{item.Index + 1}個人名字是:{item.Item}");
     }
 }

:測試方法程式碼以及示例原始碼都已經上傳至程式碼庫,有興趣的可以看看。https://gitee.com/hugogoos/Planner

相關文章