てくメモ

trivial な notes

【C#】null許容型のイディオム

null許容型(プロジェクト設定に応じてT?型または参照型)をパターンマッチングでさっと扱うコードを見て、まだ頭に馴染んでなかったと感じたので記事に書いて馴染ませる。

ついでにパターンマッチング以外も整理する。



null 条件演算子 (?. / ?[])

// IDisposable? disposable;
disposable?.Dispose();

// double[]? array;
double? sum = array?.Sum();

null 条件演算子は、null でない場合のみメンバーにアクセスする。値を受ける場合、null のときは null が返る。

A?.B?.C()のようにチェーンすることもできる。null であった場合、それ以降は評価されない

?.以外に?[]でインデックスアクセスもできる。

null 合体演算子 (??, ??=)

// string path;
string parent = Path.GetDirectoryName(path)
    ?? throw new ArgumentException(nameof(path));

// private  object? instance;
public object Instance => instance ??= new();

ともに、左オペランドが null でない場合、右オペランドは評価されない。

プロパティパターン

// string? str;
if (str is { Length: > 0 })
    Console.WriteLine("例えばこれは `!string.IsNullOrEmpty(str)` 相当の処理");

if (str is not { Length: > 0 })
    Console.WriteLine("例えばこれは `string.IsNullOrEmpty(str)` 相当の処理");

パターンマッチングのプロパティパターンは、null チェックが行われる。

複雑なパターンを一気に書こうとしなければ、簡潔に書けて意図も明瞭。

コンパイラも素直に展開してくれる。ベタ書きより悪くなることはそう考えられなさそう。

コンパイラ展開コード折りたたみ(利用:SharpLab)

if (str != null && str.Length > 0)
{
    Console.WriteLine("例えばこれは `!string.IsNullOrEmpty(str)` 相当の処理");
}
if (str == null || str.Length <= 0)
{
    Console.WriteLine("例えばこれは `string.IsNullOrEmpty(str)` 相当の処理");
}

リストパターン

// int[]? array;
if (array is [var first, ..])
    Console.WriteLine(first);

パターンマッチングのリストパターンも、null チェックが行われる。

ただ、次に挙げるSpanを併用したほうがよい。これは、特に範囲を代入する場合(上記の例なら例えば[var first, .. var rest]のようにする場合)、書き手の意図する挙動は大抵、Span経由の挙動であろうため1

Span

// double[]? nums;
var span = nums.AsSpan();
span.Sort();

// int[]? array;
if (array.AsSpan() is [var seed, .. var rest])
{
    foreach (var i in rest)
        seed *= i;
    Console.WriteLine(seed);
}

Spanは null を透過的に処理しうる。

なお、AsSpan()は拡張メソッドなので、null が呼んでも問題ない。



  1. 配列やList<T>などは、Spanと異なりnewされる。