c#でヒープにも乗る固定長リストを作ってみた

# 注意
[前回のコード](https://qiita.com/utasimaru/items/afc300ce242d6a30f2fc)はref構造体なので，絶対ヒープに乗らず，リストを使用するよりもパフォーマンスが優れているはずですが，今回のコードは微妙です．
ただなんとなく「最大サイズが決まってるリストにリストを使いたくない」という気持ちで書いたもので，パフォーマンスについて検証してません．

# 使用例
バッファ用の構造体を用意し，インターフェースを継承してAsSpan<T>を公開する．
inlineArrayが使える場合はスマートに書けます.
(※コンストラクタがグロテスクになるのでstatic関数でリストを作成するとよいかも) 
```c#
public struct Buffer4<T> : IStructBuffer<T> // Tが4つ入るバッファ
{
    public T t0, t1, t2, t3;
    public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref t0, 4);
    public static FixedList<T, Buffer4<T>> List() => new FixedList<T, Buffer4<T>>();
}
public struct Buffer16<T> : IStructBuffer<T> // Tが16個入るバッファ
{
    public Buffer4<T> t0, t1, t2, t3;
    public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref t0.t0, 16);
    public static FixedList<T, Buffer16<T>> List() => new FixedList<T, Buffer16<T>>();
}

var list4 = Buffer4<int>.List();
list4.Add(1);
list4.Add(2);
list4.Add(1);
list4.Add(1);
list4.RemoveRange(1, 2);
list4.Insert(1, 3);
foreach(var i in list4.AsSpan())
{
    Console.WriteLine(i); // 1,3,1
}
```
# コード
```c#
public interface IStructBuffer<T>
{
    Span<T> AsSpan();
}
public struct FixedList<T,BufferT> where BufferT:IStructBuffer<T>
{
    private int count;
    private BufferT buffer;

    public Span<T> AsSpan() => buffer.AsSpan().Slice(0, count);
    public int Count => count;
    public bool ContainsIndex(int i) => 0 <= i && i < count;

    // Edit Method
    public void Clear() => count = 0;
    public void Add(in T add)
    {
        var span = buffer.AsSpan();
        if (span.Length < count + 1) throw new IndexOutOfRangeException("確保バッファを超過");
        span[count++] = add;
    }
    public void RemoveRange(int start, int range)
    {
        if (!ContainsIndex(start)) throw new IndexOutOfRangeException("削除開始インデックスがリスト外");
        if (!ContainsIndex(start + range - 1)) throw new IndexOutOfRangeException("削除開始インデックスから指定範囲を行くとはみ出ます");
        count -= range;
        var span = buffer.AsSpan();
        int backCount = span.Length - start - range;
        Span<T> copyFrom = span.Slice(start + range, backCount);// 削除範囲含まない後ろの全部
        Span<T> copyTo = span.Slice(start, backCount);// 削除開始を含む後ろ
        copyFrom.CopyTo(copyTo);
        for(int i = 0; i < range; i++)
        {
            span[count + i] = default;
        }
    }
    public void Insert(int index, in T insert)
    {
        int add = 1;
        int newCount = count + add;
        var span = buffer.AsSpan();
        int bufferCapacity = span.Length;
        if (bufferCapacity < newCount) throw new IndexOutOfRangeException();
        count = newCount;

        int backCount = bufferCapacity - index - add;
        Span<T> copyFrom = span.Slice(index, backCount);
        Span<T> copyTo = span.Slice(index + add, backCount);
        copyFrom.CopyTo(copyTo);
        span[index] = insert;
    }
    public void Insert(int index, in T t0, in T t1, in T t2)
    {
        int add = 3;
        int newCount = count + add;
        var span = buffer.AsSpan();
        int bufferCapacity = span.Length;
        if (bufferCapacity < newCount) throw new IndexOutOfRangeException();
        count = newCount;

        int backCount = bufferCapacity - index - add;
        Span<T> copyFrom = span.Slice(index, backCount);
        Span<T> copyTo = span.Slice(index + add, backCount);
        copyFrom.CopyTo(copyTo);
        span[index++] = t0;
        span[index++] = t1;
        span[index] = t2;
    }
}
```