這一節要來談 Inline Array。
Inline Array 是一種類似陣列但具有更高效能,在中文被稱為內嵌陣列或內聯陣列。具有以下特點:
- InlineArray 本身只能以 struct 宣告
- 內容只能有一個欄位,但欄位的型別不限
- 不具備 Length 屬性
- 編譯器不會為 InlineArray 實作 IEnumerable 或 IEnumerable<T> 介面
- 可轉換為 Span<T> 或 ReadOnlySpan<T> 使用
宣告
Inline Array 的宣告大致是三個要點:
- 宣告型別必須是 strcut
- 只有一個內容欄位,名稱、型別與存取修飾不限
- 加上 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}#");
}
很有趣的新功能,大家有空可以試試。