C# 7.2から追加された読み取り専用参照(ref readonly T
)ですが、残念ながらそのままReadOnlySpan<T>
を構築出来ません。
読み取り専用ではない通常の参照(ref T
)であれば、Span<T>
をMemoryMarshal.CreateSpan
で構築出来ますが、ReadOnlySpan<T>
を構築するMemoryMarshal.CreateReadOnlySpan
の引数はref T
となっているため、読み取り専用参照(ref readonly T
)からは構築出来ません。
また、.NET Standard 2.0ではMemoryMarshal.CreateReadOnlySpan
がありません。
ReadOnlySpanを構築する方法
読み取り専用参照から通常の参照を得るため、System.Runtime.CompilerServices.Unsafe.AsRef(in reference)
を使用します。
NugetでSystem.Runtime.CompilerServicesを追加する必要があります。
.NET Standard 2.0 向け
MemoryMarshal.CreateReadOnlySpan
が無いため、ポインターからReadOnlySpan<T>
を構築します。
Unsafeの許可が必要になります。
追記: 参照先がスタック上に存在しないと不正なポインターとなりクラッシュする可能性が有ります。
配列やclassに含まれる値型の参照からReadOnlySpan
を構築してはいけません。
public static ReadOnlySpan<T> CreateReadOnlySpan<T>(in T reference, int length)
where T : unmanaged
{
unsafe
{
return new ReadOnlySpan<T>(Unsafe.AsPointer(ref Unsafe.AsRef(in reference)), length);
}
}
.NET Standard 2.1 向け
public static ReadOnlySpan<T> CreateReadOnlySpan<T>(in T reference, int length)
where T : unmanaged
{
return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in reference), length);
}
他のユーティリティメソッド
オマケです。
Unsafeクラスのメソッドはほぼ通常の参照(ref T
)しか引数に取らないため、読み取り専用参照を扱うメソッドを用意してみました。
public static class ReadOnlyRefUnsafe
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<T> CreateReadOnlySpan<T>(in T reference, int length)
where T : unmanaged
{
#if BEFORE_NET_STANDARD21
unsafe
{
return new ReadOnlySpan<T>(Unsafe.AsPointer(ref Unsafe.AsRef(in reference)), length);
}
#else
return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in reference), length);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T Add<T>(in T source, int elementOffset)
=> ref Unsafe.Add(ref Unsafe.AsRef(in source), elementOffset);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T Add<T>(in T source, IntPtr elementOffset)
=> ref Unsafe.Add(ref Unsafe.AsRef(in source), elementOffset);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T AddByteOffset<T>(in T source, IntPtr byteOffset)
=> ref Unsafe.AddByteOffset(ref Unsafe.AsRef(in source), byteOffset);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AreSame<T>(in T left, in T right)
=> Unsafe.AreSame(ref Unsafe.AsRef(in left), ref Unsafe.AsRef(in right));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly TTo As<TFrom, TTo>(in TFrom source)
=> ref Unsafe.As<TFrom, TTo>(ref Unsafe.AsRef(in source));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr ByteOffset<T>(in T origin, in T target)
=> Unsafe.ByteOffset(ref Unsafe.AsRef(in origin), ref Unsafe.AsRef(in target));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAddressGreaterThan<T>(in T left, in T right)
=> Unsafe.IsAddressGreaterThan(ref Unsafe.AsRef(in left), ref Unsafe.AsRef(in right));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAddressLessThan<T>(in T left, in T right)
=> Unsafe.IsAddressLessThan(ref Unsafe.AsRef(in left), ref Unsafe.AsRef(in right));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Read<T>(in byte source)
=> As<byte, T>(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T Subtract<T>(in T source, int elementOffset)
=> ref Unsafe.Subtract(ref Unsafe.AsRef(in source), elementOffset);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T Subtract<T>(in T source, IntPtr elementOffset)
=> ref Unsafe.Subtract(ref Unsafe.AsRef(in source), elementOffset);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T SubtractByteOffset<T>(in T source, IntPtr byteOffset)
=> ref Unsafe.SubtractByteOffset(ref Unsafe.AsRef(in source), byteOffset);
}