UnsafeAccessor でジェネリックが使えなかった話

# はじめに

`UnsafeAccessor` を活用して `List<T>` の内部配列を取れないものかと思ったのですが、ジェネリックな型を含む場合は使えないようでした。実行時 [`System.BadImageFormatException`](https://learn.microsoft.com/ja-jp/dotnet/api/system.badimageformatexception?view=netcore-3.0)(Invalid usage of UnsafeAccessorAttribute.) になります。

:::note info
追記: ↓ の方法でやりたいことはできるようです。

```cs
List<int> list = [1, 2, 3];
var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(list);
```

:::

## UnsafeAccessor とは

- ドキュメント <https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.compilerservices.unsafeaccessorattribute?view=net-8.0>
- .NET 8.0 で追加された新機能
- リフレクションなしでプライベートメンバにアクセスできる
- AoT でリフレクションを避けたい需要から追加された機能

```cs
using System.Runtime.CompilerServices;

class TestClass
{
    private int _value;
    public int Value { get => this._value; set => this._value = value; }
}

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_value")]
extern static ref int _value(TestClass value);

var test = new TestClass();

_value(test) = 1;
```

## `List<T>` の内部配列を取りたい

`List<T>` はよく使う型なのですが、`Span<T>` を取りたいときに配列のコピー（`ToArray()`）を作らないといけないもどかしさがあります。内部配列を取得できればこれを省略できそうです。

`extern` メソッドでは、ジェネリック型を指定するとうまくいかないようです。
それもそのはずで、ジェネリック型は実行時に型を決定しますが、`extern` はコンパイル時に型を決定します（うろ覚え）。
ジェネリック型でない、具体的な型（例: `int`）を与えるとうまくいきます。

```cs
class A<T>
{
    [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_items")]
    internal static extern ref T[] _items(List<T> list);
}

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_items")]
internal static extern ref int[] _items(List<int> list);

 var list = new List<int>();
A<int>._items(list); // こちらは例外
_items(list); // こちらはいける
```

## おわりに

`List<T>` の内部配列は取れないこともないですが、型ごとに取得メソッドを定義する必要があり、あまり実用的ではなさそうです。

## おまけ `UnsafeAccessor` のパフォーマンス比較

<details>
<summary>テストコード</summary>

```cs
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

class TestClass
{
    private int _value;
    public int Value { get => this._value; set => this._value = value; }
}

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_value")]
extern static ref int _value(TestClass value);

static Action AssignProperty()
{
    var test = new TestClass();

    return () => test.Value++;
}

static Action AssignUnsafeAccessor()
{
    var test = new TestClass();

    return () => _value(test)++;
}

static Action AssignExpression()
{
    var test = new TestClass();
    var info = test.GetType().GetField("_value", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;

    Func<TestClass, int> getter;
    {
        var p = Expression.Parameter(typeof(TestClass));
        getter = Expression.Lambda<Func<TestClass, int>>(Expression.Field(p, info), p).Compile();
    }

    Action<TestClass, int> setter;
    {
        var p1 = Expression.Parameter(typeof(TestClass));
        var p2 = Expression.Parameter(typeof(int));
        setter = Expression.Lambda<Action<TestClass, int>>(Expression.Assign(Expression.Field(p1, info), p2), p1, p2).Compile();
    }

    return () => setter(test, getter(test) + 1);
}

static Action AssignReflection()
{
    var test = new TestClass();
    var info = test.GetType().GetField("_value", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;

    return () =>
    {
        int value = (int)info.GetValue(test)!;
        info.SetValue(test, value + 1);
    };
}
```

</details>

|Test|Score|%|CG0|
|---|---:|---:|---:|
|AssignProperty|2,566,308|100.0%|0|
|AssignUnsafeAccessor|2,557,564|99.7%|0|
|AssignExpression|2,578,716|100.5%|0|
|AssignReflection|857,693|33.4%|4|

- `UnsafeAccessor` はプロパティと遜色ないパフォーマンスです
- 式木もプロパティと遜色ないパフォーマンスです。これは意外でした。初期化処理分を計測していないため、実際はもう少しパフォーマンスは落ちると思います
- リフレクションはややパフォーマンスは落ちますが、昔に比べると一桁くらい良くなってる気がします。BOX 化の影響でガベージが発生します

実行環境: Windows11 x64 .NET Runtime 8.0.0  
**Score** は高いほどパフォーマンスがよいです。  
**GC0** はガベージコレクション回数を表します（少ないほどパフォーマンスがよい）。