LoginSignup
2
3

Span<T> から配列を取得する

Last updated at Posted at 2024-01-23

はじめに

ぷろぐらまもすなる日記といふものを、我もしてみむとてするなり。

はじめまして、自分はしがないプログラマをしております。休日には主に自転車を漕いでます。

今まで技術系の記事を書いたことがなかったのですが、年も明けましたし書いてみようと思います。読み取り専用を脱却して、これからは書き込み可能にしていきます。

このブログの目的

  • 記事を書くことで自分の理解を深める + アウトプットの練習
  • 備忘録
  • 書き溜めて技術系の同人誌を出してみたい

自分は主に C# をやってきたので、それ中心の記事になると思います。最近 Godot を触り始めたので、そちらの記事も書いていきたいと思います。

Span<T> から配列を取得する

結構あれな内容になります。
変数は Span<T> としつつ中身は T[] である場合に、どうにかして T[] を取得する話です。

Span<int> span = new int [] { 1, 2, 3 };

// これはできない
int[] array = span;

このコードはかなりあれなので、実際には使わないでください。

Span<T> から配列を取得するメソッド

using System.Runtime.CompilerServices;

unsafe bool TryGetArray<T>(Span<T> span, out T[]? value)
{
    value = null;
    if (span.Length is 0)
    {
        return false;
    }

    var ptr = (IntPtr*)Unsafe.AsPointer(ref span.GetPinnableReference());
    // 配列の長さ
    if (*(ptr - 1) != span.Length)
        return false;

    // 型情報
    var empty = Array.Empty<T>();
    if (*(ptr - 2) != *(IntPtr*)Unsafe.As<T[], IntPtr>(ref empty))
        return false;

    var tmp = (IntPtr)(ptr - 2);
    value = Unsafe.As<IntPtr, T[]>(ref tmp);
    return true;

    // NOTE: .NET 8 時点のメモリレイアウト
    // T: 型情報アドレス
    // L: 配列の長さ
    // array = [7, 8, 9]
    //          -2  -1  0   1   2   3
    // array    *   *   T   L   7   8
    // array[0] T   L   7   8   9   *
    // span[0]  T   L   7   8   9   *
    // empty    *   *   T   L   *   *
}

テスト

using Xunit;

[Fact]
void TryGetArrayTest()
{
    Span<int> wantSpan = [1, 2, 3,];
    Assert.False(TryGetArray(wantSpan, out var gotSpan));
    Assert.Null(gotSpan);

    int[] wantArray = [7, 8, 9,];
    Assert.True(TryGetArray(wantArray.AsSpan(), out var gotArray));
    Assert.Equal(wantArray, gotArray);

    string[] wantStringArray = ["1", "2", "3",];
    Assert.True(TryGetArray(wantStringArray.AsSpan(), out var gotStringArray));
    Assert.Equal(wantStringArray, gotStringArray);
}
  • Array.Empty<T>() は同じインスタンスを返すため高速です
  • T[] の先頭アドレスは実行時型情報インスタンスのアドレスが入っています
  • typeof(T[]) == new T[0].GetType()true ですが、それぞれインスタンスは異なります
  • このメソッドの使いどころ: 使わないでください
    • Span<T>ref struct なのでヒープに持てないため、配列に読み替える利点があります
    • stringchar[] と似たようなメモリレイアウトなので、同じことができたりします

おわりに

こういう方針でいきます。よろしくお願いします。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3