はじめに
switch 式のコードを読んでいたら、when が付いたものを見かけました。
var label = score switch
{
var n when n >= 90 => "A",
_ => "C"
};
switch 式は パターン => 値 の形だと思っていたので、途中に条件が挟まるこの書き方が気になりました。
そんな書き方ができるのか、できるとしてどういうときに使うのかを調べたのでまとめます。
when は「パターンに一致したあとの追加条件」
when はガード節(case guard)と呼ばれるもので、パターンに一致したうえでさらに条件を課したいときに書きます。パターンと => の間に置きます。
string Judge(int score) => score switch
{
var n when n >= 90 => "A",
var n when n >= 70 => "B",
_ => "C"
};
var n でマッチした値を n として受け取り、when n >= 90 で絞り込む、という流れです。when の後ろには bool を返す式なら書けます。
パターンで書けるなら when は要らない
さきほどの例は、そもそも関係パターンで書けます。
string Judge(int score) => score switch
{
>= 90 => "A",
>= 70 => "B",
_ => "C"
};
var n when n >= 90 と >= 90 は同じことをしているので、後者のほうが短く読めます。パターン構文で表現できる条件なら、素直にパターンで書いたほうがよさそうだと感じました。
when が必要になるケース
逆に、パターンとしては書けない条件のときに when の出番になります。調べていて「これは when じゃないと書けないな」と思ったものを挙げます。
メソッドの戻り値で判定したい
パターンにメソッド呼び出しは書けないので、when に寄せることになります。
string Check(string? s) => s switch
{
null => "null",
var v when string.IsNullOrWhiteSpace(v) => "空白のみ",
_ => "値あり"
};
同じオブジェクトのメンバー同士を比較したい
プロパティパターンは「そのプロパティが定数や範囲に一致するか」は書けますが、「プロパティ A とプロパティ B を比べる」は書けません。
string Check(Order order) => order switch
{
{ Quantity: <= 0 } => "数量が不正",
var o when o.Total > o.Limit => "上限超過",
_ => "OK"
};
{ Quantity: <= 0 } はパターンで完結しますが、Total と Limit の比較は標準のパターン構文だけでは直接書けないため、通常は when を使います。IsOverLimit のような計算済みプロパティを型に足せばプロパティパターンで書く手もありますが、そこまでするかは状況次第だと思います。
プロパティパターンと組み合わせる
パターンで取り出した変数を when の条件に使うこともできます。
string Check(Person p) => p switch
{
{ Age: >= 18, Name: var name } when name.StartsWith("A") => "成人・A始まり",
{ Age: >= 18 } => "成人",
_ => "未成年"
};
Name: var name で名前を変数に受けておき、それを when 側で使っています。パターンで大枠を絞って、パターンでは表現できない部分だけ when に書く、という分担になります。
気をつけたい点
上から順に評価される
アームは書いた順に評価され、最初に一致したものが採用されます。広い条件を上に書くと、下のアームに到達しません。
// 上が常に先に一致するので、下の "A" には届かない
string Judge(int score) => score switch
{
var n when n >= 70 => "B",
var n when n >= 90 => "A",
_ => "C"
};
パターンだけで書いた場合はコンパイラが到達不能を検知してくれることがありますが、非定数の when の条件は実行時に評価されるため、順番の間違いは自分で気をつける必要があります。
非定数の when は網羅性の根拠にならない
switch 式は、すべての入力値を処理できているかをコンパイラがチェックします。ここで、条件が実行時に決まる when 付きのアームは、網羅性を判断するための確実なアームとはみなされません。
string Check(bool flag) => flag switch
{
true when DateTime.Now.Hour < 12 => "午前",
false => "false"
};
true は上のアームで扱っているように見えますが、when の条件次第では一致しないため、true が未処理として扱われ、警告 CS8846(switch 式が網羅的ではなく、when 句を持つパターンが一致する可能性がある)が出ます。
なお、ガードが定数式の true(when true)のように「必ず成立する」場合は、ガードなしのアームと同じ扱いになります。網羅性の根拠にならないのは、あくまで実行時に決まる条件のときです。
未処理の値をどう扱うか
上のような未処理の値を防ぐ手段として _ のアームがよく挙げられます。想定外の値をまとめてフォールバックさせたいなら _ が適切です。
一方で、列挙型のように「新しい値が追加されたらコンパイラ警告で気づきたい」ケースでは、安易に _ を置くと警告が消えてしまいます。その場合は _ に頼らず、必要なアームを明示するか、最後のアームで例外を投げて実行時に気づけるようにする、といった選び方になります。用途によって使い分けるのがよさそうです。
switch 文でも同じように書ける
when は switch 式専用の構文ではなく、switch 文の case にも書けます。
switch (score)
{
case int n when n >= 90:
return "A";
case int n when n >= 70:
return "B";
default:
return "C";
}
switch 式で見かけた when は、switch 文の頃から使えたものがそのまま使える形になっている、という理解をしました。
まとめ
-
whenはパターンに一致したあとに追加条件を課すガード節で、switch 式でも switch 文でも書ける - 関係パターンなどで表現できる条件なら、
whenを使わずパターンで書いたほうが短い - メソッドの戻り値やメンバー同士の比較など、パターンでは書けない条件のときに
whenを使う - アームは上から順に評価されるので、
whenを並べるときは順番に注意する - 非定数の
when付きアームは網羅性の根拠にならないため、未処理の値が出うる(when trueは例外) - 未処理の値はフォールバックなら
_、想定外を検出したいなら明示アームや例外、と用途で使い分ける
when を見たときは「switch 式の中に if が混ざっているのか?」という印象でしたが、パターンで書けない条件を逃がすための場所だと分かると納得できました。まずはパターンで書けないか考えて、無理なら when、という順で書こうと思います。
参考になったら いいね や ストック をお願いします!
同じような疑問を持ったことがある方のコメントもお待ちしています。
参考
- switch 式 - C# リファレンス(Microsoft Learn)
- パターン マッチング - C# リファレンス(Microsoft Learn)
- switch ステートメント - C# リファレンス(Microsoft Learn)
- パターン マッチングの警告を解決する - C# リファレンス(Microsoft Learn)
関連リンク
技術ブログでも学びや検証内容をまとめています。