这一节要来谈 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}#");
 }

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