簡介
內聯數組是 C# 12 和 .NET 8 中引入的一個高級特性,它允許開發者創建固定大小的、在棧上分配或內聯在結構體中的數組。這個特性主要用於高性能場景,可以避免堆分配和垃圾回收的開銷。
性能優勢
內聯數組的主要優勢在於性能:
- 棧上分配:避免堆分配和垃圾回收
- 內存局部性:元素在內存中連續存儲,提高緩存命中率
- 減少指針間接尋址:直接訪問元素,不需要通過數組對象引用
內聯數組 vs 傳統數組
| 特性 | 內聯數組 | 傳統數組 |
|---|---|---|
| 內存位置 | 棧/包含結構體內存 | 託管堆 |
| 分配開銷 | 無額外分配 | 需要堆分配 |
| 最大長度 | 受棧空間限制(通常≤1MB) | 受GC限制(通常≤2GB) |
| 適用場景 | 高性能計算/嵌入式開發 | 通用場景 |
| 靈活性 | 固定長度 | 動態長度 |
| C#版本要求 | ≥ C# 12 (.NET 8+) | 所有版本 |
基本語法
內聯數組使用 InlineArray 特性和特定的模式來定義:
using System.Runtime.CompilerServices;
[InlineArray(Size)]
struct InlineArrayStruct
{
private T _element; // 單一字段,表示數組的起點
}
Size:一個編譯時常量,表示數組的固定長度。T:數組元素的類型(可以是值類型或引用類型)。- 單一字段:結構體中只能有一個字段,表示數組的起點,編譯器會將其擴展為固定大小的連續內存。
[System.Runtime.CompilerServices.InlineArray(10)]
public struct MyInlineArray
{
private int _element0; // 只需要定義一個字段,實際大小由特性指定
}
創建和使用內聯數組
基本定義和使用
using System;
using System.Runtime.CompilerServices;
// 定義包含10個整數的內聯數組
[InlineArray(10)]
public struct IntArray10
{
private int _element0;
// 可以添加方法或屬性來增強功能
public int Length => 10;
public int Sum()
{
int sum = 0;
for (int i = 0; i < Length; i++)
{
sum += this[i];
}
return sum;
}
}
class Program
{
static void Main()
{
IntArray10 array = new IntArray10();
// 初始化數組
for (int i = 0; i < array.Length; i++)
{
array[i] = i * 2;
}
// 訪問元素
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine($"array[{i}] = {array[i]}");
}
Console.WriteLine($"總和: {array.Sum()}");
}
}
與 Span<T> 結合
[InlineArray(5)]
struct FloatBuffer
{
private float _element;
}
var buffer = new FloatBuffer();
buffer[0] = 1.5f;
buffer[1] = 2.5f;
Span<float> span = buffer;
Console.WriteLine(span[0]); // 輸出: 1.5
Console.WriteLine(span.Length); // 輸出: 5
泛型內聯數組
[InlineArray(5)]
public struct GenericArray<T>
{
private T _element0;
public int Length => 5;
public void Initialize(T initialValue)
{
for (int i = 0; i < Length; i++)
{
this[i] = initialValue;
}
}
}
// 使用示例
GenericArray<string> stringArray = new GenericArray<string>();
stringArray.Initialize("default");
在性能敏感場景中使用
內聯數組適合需要極致性能的場景,例如處理向量或緩衝區。
[InlineArray(3)]
struct Vector3D
{
private float _element;
}
Vector3D AddVectors(Vector3D a, Vector3D b)
{
var result = new Vector3D();
for (int i = 0; i < 3; i++)
{
result[i] = a[i] + b[i];
}
return result;
}
var v1 = new Vector3D { [0] = 1.0f, [1] = 2.0f, [2] = 3.0f };
var v2 = new Vector3D { [0] = 4.0f, [1] = 5.0f, [2] = 6.0f };
var sum = AddVectors(v1, v2);
Console.WriteLine($"({sum[0]}, {sum[1]}, {sum[2]}"); // 輸出: (5, 7, 9)
Vector3D模擬一個三維向量,內聯數組確保內存連續。- 適合遊戲開發或科學計算。
與本機代碼交互
內聯數組的連續內存佈局使其非常適合與本機代碼(如 C/C++ )交互。
using System.Runtime.InteropServices;
[InlineArray(8)]
struct CharBuffer
{
private char _element;
}
[DllImport("someNativeLib.dll")]
extern static void ProcessBuffer(ref CharBuffer buffer);
var buffer = new CharBuffer();
buffer[0] = 'H';
buffer[1] = 'e';
buffer[2] = 'l';
buffer[3] = 'l';
buffer[4] = 'o';
ProcessBuffer(ref buffer);
CharBuffer的內存佈局與C語言中的char[8]兼容。- 通過
ref傳遞,確保本機代碼可以直接操作內存。
高級用法
與不安全代碼結合
[InlineArray(8)]
public unsafe struct DoubleArray
{
private double _element0;
public fixed int Length => 8;
public double* GetPointer()
{
fixed (double* ptr = &this[0])
{
return ptr;
}
}
}
模擬多維數組
// 使用一維內聯數組模擬二維數組
[InlineArray(16)] // 4x4 矩陣
public struct Matrix4x4
{
private float _element0;
public int Rows => 4;
public int Columns => 4;
public float this[int row, int col]
{
get => this[row * Columns + col];
set => this[row * Columns + col] = value;
}
public static Matrix4x4 Identity()
{
var matrix = new Matrix4x4();
for (int i = 0; i < 4; i++)
{
matrix[i, i] = 1.0f;
}
return matrix;
}
}
與 Span 和 Memory 互操作
[InlineArray(100)]
public struct Buffer100
{
private byte _element0;
public int Length => 100;
public Span<byte> AsSpan()
{
return MemoryMarshal.CreateSpan(ref this[0], Length);
}
public ReadOnlySpan<byte> AsReadOnlySpan()
{
return MemoryMarshal.CreateReadOnlySpan(ref this[0], Length);
}
}
底層原理
編譯後代碼結構
// 原始代碼
[InlineArray(5)]
public struct Buffer5 { private int _element; }
// 近似編譯結果
public struct Buffer5
{
private int _element0;
private int _element1;
private int _element2;
private int _element3;
private int _element4;
public ref int this[int index]
{
get
{
if ((uint)index >= 5)
throw new IndexOutOfRangeException();
return ref Unsafe.Add(ref _element0, index);
}
}
}
適用場景
- 高性能計算:遊戲引擎、圖形處理或科學計算中需要連續內存的場景。
- 緩衝區管理:處理固定大小的緩衝區,如網絡數據包或文件讀寫。
- 與本機代碼交互:與
C/C++或其他本機庫交互,傳遞連續內存塊。 - 替代小型數組:在結構體中替代
T[]字段,減少堆分配。 - 向量/矩陣操作:表示數學向量或矩陣,優化內存訪問。
注意事項
僅限結構體:
- 只能定義在
struct中,不能用於class。 - 這是因為結構體是值類型,內存分配更可控。
固定大小:
- 內聯數組的大小在編譯時必須是常量,無法動態調整。
- 不適合需要變長數組的場景。
單一字段限制:
- 帶有
[InlineArray]的結構體只能包含一個字段。 - 如果需要其他字段,必須使用嵌套結構體。
[InlineArray(10)]
struct InvalidBuffer
{
private int _element;
private int _otherField; // 錯誤:只能有一個字段
}
性能優勢:
- 內聯數組分配在棧上(對於局部變量)或嵌入結構體中,減少堆分配。
- 連續內存佈局減少緩存未命中(
cache miss),提高性能。
版本要求:
- 內聯數組是
C# 12(.NET 8)的新特性,需確保項目目標框架為.NET 8.0或更高。 - 需要
System.Runtime.CompilerServices.InlineArray特性。
索引越界:
- 內聯數組支持索引訪問,但不會自動檢查越界。
- 使用
Span<T>操作時,Span<T>會提供邊界檢查。
[InlineArray(2)]
struct SmallBuffer
{
private int _element;
}
var buffer = new SmallBuffer();
buffer[2] = 1; // 運行時異常:索引越界
與普通數組的對比:
- 普通數組(
T[]):分配在託管堆上,動態大小,支持垃圾回收。 - 內聯數組:固定大小,嵌入結構體,連續內存,適合高性能場景。
與其他特性的對比
與普通數組(T[])的對比:
- 普通數組是引用類型,分配在堆上,大小可動態調整。
- 內聯數組是值類型的一部分,固定大小,內存連續,性能更高。
與固定大小緩衝區(fixed)的對比:
C#的fixed關鍵字用於在unsafe上下文中創建固定大小緩衝區。- 內聯數組是類型安全的,無需
unsafe,更易用。
與 Span<T>/Memory<T> 的對比:
- 內聯數組是數據的存儲結構,而
Span<T>和Memory<T>是訪問視圖。 - 內聯數組可直接轉換為
Span<T>,結合使用效率更高。
與棧分配(stackalloc)的對比:
stackalloc在棧上分配臨時內存,生命週期短。- 內聯數組嵌入結構體,支持更靈活的生命週期和傳遞。