C#内存相关类型理解

memory access pattern

In .NET world, there are three types of memory you may be interested:

  1. Managed heap memory, such as an array;
  2. Stack memory, such as objects created by stackalloc;
  3. Native memory, such as a native pointer reference.

Each type of memory access may need to use language features that are designed for it:

To access heap memory, use the fixed (pinned) pointer on supported types (like string), or use other appropriate .NET types that have access to it, such as an array or a buffer; To access stack memory, use pointers with stackalloc; To access unmanaged system memory, use pointers with Marshal APIs. You see, different access pattern needs different code, no single built-in type for all contiguous memory access.

System.Memory<T>

readonly struct Memory<T> : IEquatable<Memory<T>>
{
    // The highest order bit of _index is used to discern whether _object is a pre-pinned array.
    // (_index < 0) => _object is a pre-pinned array, so Pin() will not allocate a new GCHandle
    //       (else) => Pin() needs to allocate a new GCHandle to pin the object.
    private readonly object? _object;
    private readonly int _index;
    private readonly int _length;
}

注解

与 Span 类似, Memory 表示一段连续的内存。 不同点在于 Memory 不是 ref struct。 这意味着, Memory 可以放置在托管堆上,而 Span 不能。 因此,Memory 没有 Span 的那些限制。 具体而言:

  • 它可用作类中的字段。
  • 它可以跨 await 和 yield 边界使用 。 除了Memory之外 ,还可以使用 System.ReadOnlyMemory 来表示不可变或只读内存。

object可以是:

  1. T[]
  2. MemoryManager<T>
  3. T=char && object=string
  4. T=byte/Char8 && object=Utf8String

隐式转换:

  1. ArraySegment<T> to Memory<T>
  2. Memory<T> to ReadOnlyMemory<T>
  3. T[] to Memory<T>

ArraySegment<T>

表示一维数组中的一段区域

readonly struct ArraySegment<T> : IList<T>, IReadOnlyList<T>
{
    private readonly T[]? _array;
    private readonly int _offset; 
    private readonly int _count;
}

MemoryManager<T>

MemoryManager 是一个抽象基类,实现 MemoryManager 的类型可以达到替换 Memory 作用。

abstract class MemoryManager<T> : IMemoryOwner<T>, IPinnable
{
    public virtual Memory<T> Memory => new Memory<T>(this, GetSpan().Length);
    public abstract Span<T> GetSpan();
    public abstract MemoryHandle Pin(int elementIndex = 0);
    public abstract void Unpin();

    protected Memory<T> CreateMemory(int length) => new Memory<T>(this, length);
    protected Memory<T> CreateMemory(int start, int length) => new Memory<T>(this, start, length);
    protected internal virtual bool TryGetArray(out ArraySegment<T> segment);
}

ref struct

ref struct是仅在堆栈上的值类型(也被称为嵌入式引用):

  1. 表现一个顺序结构的布局;(译注:可以理解为连续内存)
  2. 只能在堆栈上使用 (stack only) 。即用作方法参数和局部变量;
  3. 不能是类或正常结构的静态或实例成员;
  4. 不能是异步方法或lambda表达式的方法参数;
  5. 不能动态绑定、装箱、拆箱、包装或转换。

Span<T>

类型安全和内存安全地表示一段连续内存,它是一个ref struct, 只能在stack上分配内存, 而不能在heap上分配内存, 但是它的实例可以指向托管内存、原生内存或者栈上内存

readonly ref struct Span<T>
{
    internal readonly ByReference<T> _pointer;
    private readonly int _length;
}