C#や.NETのバージョンアップを調べていて、「そういえば、if文やnullチェックも短く書けるようになっていたな」と思い出しました。ほかの言語でもパターンマッチを見かけます。C#だと何が省けるのか、前の書き方と今の書き方を並べてみました。
型を調べた値をそのまま使えたり、switchで条件と返す値を一行ずつ並べられたり。短くなるだけでなく、何を見て分岐しているかも追いやすくなります。
書き方を比べる例なので、変数の宣言などは省いています。customerが持つのは、有効かどうかを表すIsActive、年齢のAge、注文のOrderです。動きはC# 13(.NET 9)、C# 14(.NET 10)、C# 15 preview(.NET 11 RC 2)の単体テストで確認しました。null条件付き代入はC# 14以降で確認しています。
型を調べたあと、同じ値をキャストしなくてよい
例えば、valueが文字列なら長さを出す処理です。型を調べてからキャストしていたところが、C# 7からは一行で済みます。
// C# 6以前:型を調べてから、もう一度キャストする
if (value is string)
{
var text = (string)value;
Console.WriteLine(text.Length);
}
// C# 7以降(.NET 8 / 9 / 10 / 11 共通):型判定と変数への受け取りを一緒に書く
if (value is string text)
{
Console.WriteLine(text.Length);
}
value is string textなら、文字列だったときだけtextに入ります。ifの中では、そのtextをそのまま使えます。valueがnullなら、この分岐には入りません。
表は、2026年11月6日に1,000件ループあたりの平均実行時間とメモリ割当を測った結果です。BenchmarkDotNet 0.14.0のReleaseビルド、ShortRun、InProcessNoEmitToolchainを使い、ウォームアップ3回、本測定3回、起動1回。環境はWSL2 / Ubuntu 24.04.4 LTS / Core i7-10510Uです。
全ケースを.NET SDK 11.0.100-rc.2.26504.105、LangVersion=previewでコンパイルし、実行ランタイムを.NET 8〜11 RC 2へ切り替えました。表の列は実行ランタイムです。C# 14以降の構文を.NET 8・9で動かした列は、公式サポート対象外の組合せでの検証です。
計測ではConsole.WriteLineを含めず、文字列の長さの加算や対象件数のカウントに置き換えています。代入の比較で使うCreateOrder()は、既存の文字列"ORD-001"を返すようにしました。
画面に収まらないときは、表を横にスクロールできます。
| 書き方(1,000件ループ) | .NET 8 | .NET 9 | .NET 10 | .NET 11 RC 2 | メモリ割当 |
|---|---|---|---|---|---|
if (value is string) + (string)value |
1.28 μs | 1.56 μs | 1.26 μs | 914.47 ns | 0 B |
if (value is string text) |
938.04 ns | 1.83 μs | 1.44 μs | 920.39 ns | 0 B |
null確認とプロパティの条件を、まとめて書ける
次は「顧客がnullではなく、有効で、18歳以上なら対象」という例です。C# 8でプロパティパターン、C# 9でis not nullと>= 18のような比較パターンが加わりました。
// C# 7以前:!= null とプロパティ条件を並べる
if (customer != null && customer.IsActive && customer.Age >= 18)
{
Console.WriteLine("対象");
}
// C# 8:プロパティパターンは使えるが、>= 18 の比較パターンはまだ書けない
if (customer is { IsActive: true } c && c.Age >= 18)
{
Console.WriteLine("対象");
}
// C# 9以降(.NET 8 / 9 / 10 / 11 共通)①:is not null で null 判定
if (customer is not null && customer.IsActive && customer.Age >= 18)
{
Console.WriteLine("対象");
}
// C# 9以降(.NET 8 / 9 / 10 / 11 共通)②:プロパティパターンと比較パターンをまとめる
if (customer is { IsActive: true, Age: >= 18 })
{
Console.WriteLine("対象");
}
同じcustomerを何度も書かなくても、IsActiveとAgeを見れば、どんな顧客が対象か分かります。customerがnullなら、この条件には当たりません。
nullチェックだけならis not nullも使えます。is nullとis not nullは、型に==や!=の独自実装があっても、それを呼ばずにnullかどうかを見ます。
| 書き方(1,000件ループ) | .NET 8 | .NET 9 | .NET 10 | .NET 11 RC 2 | メモリ割当 |
|---|---|---|---|---|---|
C# 7以前:customer != null && ... |
1.09 μs | 1.45 μs | 1.55 μs | 939.64 ns | 0 B |
C# 8:customer is { IsActive: true } c && c.Age >= 18 |
1.34 μs | 1.30 μs | 1.02 μs | 1.04 μs | 0 B |
C# 9+:customer is not null && ... |
1.13 μs | 1.49 μs | 1.13 μs | 1.25 μs | 0 B |
C# 9+:customer is { IsActive: true, Age: >= 18 } |
1.13 μs | 1.50 μs | 1.03 μs | 962.92 ns | 0 B |
switch式なら、条件と返す値が並ぶ
状態の番号から表示名を返すような処理なら、C# 8以降のswitch式で条件と返す値を一行ずつ対応させられます。
// C# 7以前:if を並べて返す
static string GetStatusName(int status)
{
if (status == 0) return "未処理";
if (status == 1) return "処理済み";
return "不明";
}
// C# 8以降(.NET 8 / 9 / 10 / 11 共通):switch式と破棄パターン(_)
static string GetStatusName(int status) => status switch
{
0 => "未処理",
1 => "処理済み",
_ => "不明"
};
0でも1でもなければ、最後の_で「不明」を返します。この_はどの値にも一致する破棄パターンです。
この例だと、「0なら未処理、1なら処理済み、それ以外は不明」が上から読めます。分岐の中で何行も処理するなら、ifやswitch文のほうが素直に読めることもあります。
| 書き方(1,000件ループ) | .NET 8 | .NET 9 | .NET 10 | .NET 11 RC 2 | メモリ割当 |
|---|---|---|---|---|---|
if (status == ...) の並び |
2.05 μs | 4.04 μs | 2.27 μs | 1.75 μs | 0 B |
status switch { ... _ => } |
2.36 μs | 3.10 μs | 2.70 μs | 2.25 μs | 0 B |
outの値を使わない時も、_で表せる
TryParseで、数値に変換できたかどうかだけ分かればいいことがあります。そんなときはout _です。
// C# 6以前:受け取り用の変数を宣言して渡す
int number;
bool isNumber = int.TryParse(input, out number);
// C# 7以降(.NET 8 / 9 / 10 / 11 共通):使わない出力値を out _ で破棄する
bool isNumber = int.TryParse(input, out _);
out _の_は、出てきた値を捨てるディスカードです。さっきのswitch式では、最後に残った場合を受ける条件でした。変換後の数値も使うなら、out int numberなどで受け取ります。
| 書き方(1,000件ループ) | .NET 8 | .NET 9 | .NET 10 | .NET 11 RC 2 | メモリ割当 |
|---|---|---|---|---|---|
int number; int.TryParse(input, out number) |
13.23 μs | 14.21 μs | 12.50 μs | 10.96 μs | 0 B |
int.TryParse(input, out _) |
13.31 μs | 12.09 μs | 12.84 μs | 11.31 μs | 0 B |
C# 14では、nullの時に代入しない処理も短く書ける
nullかどうかを見てから代入するところも、C# 14で短くなりました。customerがnullでないときだけOrderに代入するなら、ifで囲んでいた部分が?.を使った一行になります。これはnull条件付き代入という機能です。
// C# 13まで:if で null を確認してから代入する
if (customer is not null)
{
customer.Order = CreateOrder();
}
// C# 14以降:null条件付き代入で1行にする
customer?.Order = CreateOrder();
customerがnullなら、右辺のCreateOrder()も呼ばれません。.NET 10と.NET 11 RC 2で試すと、nullのときはCreateOrder()が呼ばれた回数は0回でした。
| 書き方(1,000件ループ) | .NET 8 | .NET 9 | .NET 10 | .NET 11 RC 2 | メモリ割当 |
|---|---|---|---|---|---|
if (customer is not null) customer.Order = ... |
3.63 μs | 4.54 μs | 3.52 μs | 3.12 μs | 0 B |
customer?.Order = CreateOrder()(C# 14+) |
4.23 μs | 3.36 μs | 4.61 μs | 3.72 μs | 0 B |
?.で代入を飛ばした後も、customerはnullかもしれません。Nullableが有効な状態で、次の行でそのままcustomer.OrderにアクセスするとCS8602が出ます。nullならエラーを返す処理では、ifでその分岐が見えるほうが読みやすいです。
どのC#から書けるか
ここまでの書き方が入ったバージョンを並べました。
| 記法 | 導入されたバージョン |
|---|---|
value is string textのような型判定と変数への受け取り |
C# 7 |
out _による値の破棄 |
C# 7 |
| プロパティパターン | C# 8 |
switch式と、その中の_パターン |
C# 8 |
is not null、>= 18などの比較・andなどの論理パターン |
C# 9 |
customer?.Order = ...というnull条件付き代入 |
C# 14 |
プロパティパターンに>= 18を組み合わせた例は、比較のパターンも使うのでC# 9以降です。
短く書けるようになった。AIのコードも条件まで見る
C#の書き方を昔と並べてみると、短く書けて、条件も追いやすくなったところがけっこうあります。今のifやswitch文で読みやすければ、そのままで十分です。
今回測った処理では、書き換え前後ともメモリ割当は0 Bでした。平均実行時間は増えたものも減ったものもあって、一番差が出たところでも1,000件あたり約2.1 μs。この例なら、読みやすさで書き方を選べそうです。
AIに実装を任せると、普段は自分で使わない書き方も出てきます。レビューでは、元のコードと条件が同じか、nullのときに何が起きるか、プロジェクトのC#で使えるかを見る。初めて見る記法なら、今回のように前の書き方と並べて動きを確かめます。
確認した公式情報
2026年11月7日確認。

コメント