C#12 新功能 (4)

這一節要來談 Inline Array。

Inline Array 是一種類似陣列但具有更高效能,在中文被稱為內嵌陣列或內聯陣列。具有以下特點:

  1. InlineArray 本身只能以 struct 宣告
  2. 內容只能有一個欄位,但欄位的型別不限
  3. 不具備 Length 屬性
  4. 編譯器不會為 InlineArray 實作 IEnumerable 或 IEnumerable<T> 介面
  5. 可轉換為 Span<T> 或 ReadOnlySpan<T> 使用
宣告

Inline Array 的宣告大致是三個要點:

  1. 宣告型別必須是 strcut
  2. 只有一個內容欄位,名稱、型別與存取修飾不限
  3. 加上 System.Runtime.CompilerServices.InlineArray Attribute,同時設定其長度

例如以下兩個例子:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct MyInlineArray
{
    // 只能有一個欄位,而且命名可以亂取
    private int _e;
}

[System.Runtime.CompilerServices.InlineArray(4)]
public struct NameArray
{
    public string name;
}
使用

你可以這樣使用 Inline Array:

 var arr = new MyInlineArray();
 for (int i = 0; i < 10; i++)
 {
     arr[i] = i;
 }

 foreach (var item in arr)
 {
     Console.WriteLine(item);
 }
 Span<int> span1 = arr;
 foreach (var item in span1)
 {
     Console.Write($"{item},");
 }
 Console.WriteLine();
 ReadOnlySpan<int> span2 = arr;
 foreach (var item in span2)
 {
     Console.Write($"{item}#");
 }

用迴圈和索引子當然是最基本的,有趣的是 Inline Array 並沒有實作 IEnumerable 或 IEnumerable<T> 介面,而且也不具備 GetEnumerator 方法,為什麼可以用 foreach ? 答案是,編譯器最終使用的是一般的 for loop。

解密 InlineArray

當我們使用 Inline Array 的時候,編譯器會產出一個有趣的輔助型別,大約會像以下的程式碼:

internal sealed class PrivateImplementationDetails
{
    // 這個依據需求,不一定會有
    internal static ReadOnlySpan<TElement> InlineArrayAsReadOnlySpan<TBuffer, TElement>(in TBuffer buffer, int length)
        {
            return MemoryMarshal.CreateReadOnlySpan<TElement>(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef<TBuffer>(in buffer)), length);
        }

    internal static Span<TElement> InlineArrayAsSpan<TBuffer, TElement>(ref TBuffer buffer, int length)
    {
        return MemoryMarshal.CreateSpan<TElement>(ref Unsafe.As<TBuffer, TElement>(ref buffer), length);
    }

    internal static ref TElement InlineArrayElementRef<TBuffer, TElement>(ref TBuffer buffer, int index)
    {
        return ref Unsafe.Add<TElement>(ref Unsafe.As<TBuffer, TElement>(ref buffer), index);
    }
}

其中 InlineArrayAsReadOnlySpan 這個方法是需要你在程式碼中有用到 ReadOnlySpan<T> 才會出現。

實際上編譯後的程式碼都是在使用這個輔助型別,有點像這樣:

 MyInlineArray buffer = new MyInlineArray();
 PrivateImplementationDetails.InlineArrayAsSpan<MyInlineArray, int>(ref buffer, 10);
 for (int index = 0; index < 10; ++index)
 {
     PrivateImplementationDetails.InlineArrayAsSpan<MyInlineArray, int>(ref buffer, 10)[index] = index;
 }
 ref MyInlineArray local = ref buffer;
 for (int index = 0; index < 10; ++index)
 {
     Console.WriteLine(PrivateImplementationDetails.InlineArrayElementRef<MyInlineArray, int>(ref local, index));
 }

 Span<int> span1 = PrivateImplementationDetails.InlineArrayAsSpan<MyInlineArray, int>(ref buffer, 10);

 foreach (var item in span1)
 {
     Console.Write($"{item},");
 }
 Console.WriteLine();
 ReadOnlySpan<int> span2 = PrivateImplementationDetails.InlineArrayAsReadOnlySpan<MyInlineArray, int>(in buffer, 10);
 foreach (var item in span2)
 {
     Console.Write($"{item}#");
 }

很有趣的新功能,大家有空可以試試。