連続したメモリ領域を表すSpan<T>
は、null
を受けることもできる。
その場合、== null
で中身の参照のnull
チェックができる。
int[]? array1 = null; int[]? array2 = new int[] { 1, 2, 3 }; ReadOnlySpan<int> span1 = array1; ReadOnlySpan<int> span2 = array2; Console.WriteLine($"{span1 == null}: span1 == null"); Console.WriteLine($"{span2 == null}: span2 == null"); // True: span1 == null // False: span2 == null
これは演算子オーバーロードによるものなので、is null
とは等価でない。
bool _ = span1 is null; // ❌ コンパイルエラー
Span のオーバーロードされた==
が何をやっているか。
左オペランドと右オペランドの長さと参照の等価をみている。
null
同士は等価になる。
参考として、長さをみていることで、スライスしていれば参照が同じでもfalse
を返す。
int[]? array3 = null; ReadOnlySpan<int> span3 = array3; ReadOnlySpan<int> span4 = span2[..^1]; Console.WriteLine($"{span1 == span3}: span1 (null) == span3 (null)"); Console.WriteLine($"{span2 == span4}: span2 == span4 (span2[..^1])"); // True: span1 (null) == span3 (null) // False: span2 == span4 (span2[..^1])
ところで、中身の参照がnull
でも Span 自体はそうではない。
プロパティはアクセスでき、null 例外は出ない。
// Length に普通にアクセスできる Console.WriteLine($"span1.Length: {span1.Length}"); // インデクサも Span のものなので try { Console.WriteLine(span1[0]); } catch(NullReferenceException) // こちらではなく { Console.WriteLine(nameof(NullReferenceException)); } catch(IndexOutOfRangeException) // こちらが飛ぶ { Console.WriteLine(nameof(IndexOutOfRangeException)); } // span1.Length: 0 // IndexOutOfRangeException
これにより、null 許容型を Span で扱うと null を吸収して処理しうる。(見方や場合によっては、処理してしまう、という捉え方もされるかもしれない)
static string toLower(ReadOnlySpan<char> str) { Span<char> buf = stackalloc char[str.Length]; str.ToLower(buf, CultureInfo.InvariantCulture); return new(buf); } string? nullStr = null; Console.WriteLine(toLower(nullStr)); // 例外は出ない