概要
C# の ref 構造体はスタックに配置する必要がある都合上、これまでのバージョンだとジェネリック引数に渡せませんでした。これが .net9 preview6 で制限が緩和されました。
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action) where TState : allows ref struct;
ReadOnlySpan<char> input = "Hello, World!";
var str = string.Create(input.Length, input, static (stringBuffer, input) => input.ToLowerInvariant(stringBuffer));
Assert.Equal("hello, world!", str);
標準ライブラリの変更とか
System.Action<T>
System.Func<T>
などの汎用のデリゲートで、T
に ref 構造体を使えるようになっています。確認していませんが、List<T>
等のジェネリック型がヒープに置かれる型を除けば、 ref 構造体を使えるようになっている可能性があります。
Action<ReadOnlySpan<int>> act = span =>
{
Assert.Equal([1, 2, 3], span);
};
act([1, 2, 3]);
Func<ReadOnlySpan<int>> func = () => [1, 2, 3];
Assert.Equal([1, 2, 3], func());
ref 構造体のフィールドにも使用できます。
file ref struct ValueHolder<T> where T : allows ref struct
{
public T Value;
public ValueHolder(T value) => Value = value;
}
var holder = new ValueHolder<Span<int>>([1, 2, 3]);
Assert.Equal([1, 2, 3], holder.Value);
インターフェイスも継承できる!
なんと ref 構造体がインターフェイスを継承できるようになりました。つまり、ジェネリック引数に「特定のメソッドがある」制約をもたせることができます。
file interface INumber
{
int GetNumber();
static int GetNumberGeneric<T>(T number) where T : INumber, allows ref struct
=> number.GetNumber();
}
file ref struct RefStructNumber : INumber
{
private Span<int> _span;
internal RefStructNumber(Span<int> span) => this._span = span;
public int GetNumber()
{
var result = 0;
foreach (var n in this._span)
result += n;
return result;
}
}
var number = new RefStructNumber([1, 2, 3]);
Assert.Equal(6, INumber.GetNumberGeneric(number));
ボックス化が起こるコードは書けませんが、ref 構造体を使える場所が増えそうです。