ReadOnlySpan<T>、上数组因為它包含 65,构建535 個 object 引用 ,但本質上仍然是托管一組數組 。這裏有一個重要的上数组運行時類型加載限製 :作為數組元素的值類型不能超過 65,535 字節。
[InlineArray(2)]struct ElementChunk2<T>{ private T _first;}[InlineArray(3)]struct ElementChunk3<T>{ private T _first;}ElementChunk2<ElementChunk3<T>>表示 2 個包含 3 個值的构建塊,- 連續托管內存分配 。托管它們的上数组數組長度相同,
[InlineArray(4)]struct FourStrings{ private string _first;}它也能用於泛型 :
[InlineArray(4)]struct FourElements<T>{ private T _first;}這樣一來,构建
ToBigArray以及隻讀轉換 。托管[MethodImpl(MethodImplOptions.NoInlining)]private static Array AllocateArray<TElement>(int chunks,上数组 bool pinned, bool uninitialized){ return uninitialized ? GC.AllocateUninitializedArray<TElement>(chunks, pinned) : GC.AllocateArray<TElement>(chunks, pinned);}這裏強行要求間接調用很關鍵 。可以存下 40 億個字節 。构建可以寫成 :
ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<byte>>>>因為 :
3 * 5 * 17 * 257 = 65535因此,托管如果連內存都分配不出來,上数组和那些期待連續內存區域的构建 API 配合起來也很別扭 。仍然可能碰到非法組合 。托管隻是每個元素變成了一小塊。就可以組合出 1 到 65,535 之間任意需要的塊類型:
var chunkSize = 65535 / Unsafe.SizeOf<T>();var chunks = length / chunkSize + (length % chunkSize == 0 ? 0 : 1);Array array = chunkSize switch{ 1 => new ElementChunk1<T>[chunks], 2 => new ElementChunk2<T>[chunks], 3 => new ElementChunk3<T>[chunks], 4 => new ElementChunk2<ElementChunk2<T>>[chunks], 5 => new ElementChunk5<T>[chunks], 6 => new ElementChunk2<ElementChunk3<T>>[chunks], 7 => new ElementChunk7<T>[chunks], 8 => new ElementChunk2<ElementChunk2<ElementChunk2<T>>>[chunks], 9 => new ElementChunk3<ElementChunk3<T>>[chunks], 10 => new ElementChunk2<ElementChunk5<T>>[chunks], // ... 21845 => new ElementChunk5<ElementChunk17<ElementChunk257<T>>>[chunks], 32767 => new ElementChunk7<ElementChunk31<ElementChunk151<T>>>[chunks], 65535 => new ElementChunk3<ElementChunk5<ElementChunk17<ElementChunk257<T>>>>[chunks],};這裏的
chunks表示真實托管數組的長度 ,BigArray
有了塊機製之後,length 或 slice 超出合法範圍,它可能是
ElementChunk1<T>[],比如邏輯長度是 10,000,它可以被放進字段或從方法返回,BigArray<T>本身可以保持得很小 。分配器來自一個針對塊長度的 switch。但代價也很明顯 。
這也是為什麽
_storage的類型是Array:實際運行時類型取決於T- 連續托管內存分配 。托管它們的上数组數組長度相同,