はじめに
現在私はXD.GROWTHで主にGo言語を使っています。
長らくC#から離れています。
最後に本格的に触っていたのは C# / .NET Framework 3.5 の頃です。
Go言語を使っていると、C#ならこんな書き方できるのになぁと思うことも多々あります。
このシリーズでは数回に分けてC#を久しぶりに触ってみて
- 「こんな書き方ができるようになったのか」と驚いた点
- 久しぶりに触ってみて、正直「やっぱり不便だな」と感じた点
を中心に、コードを交えながら紹介します。
今回のテーマ
今回のテーマは パターンマッチング です。
特にこのあたり:
-
isがただの型チェックではなくなっている -
&&/||の代わりにand/or/not - 条件式の中で変数が生える
- しかもその変数、if の外でも使える
正直、最初はかなり混乱しました。
昔の書き方(.NET 3.5 時代)
まずは懐かしいコードから。
if (obj != null && obj is string)
{
string s = (string)obj;
Console.WriteLine(s.Length);
}
または
var s = obj as string;
if (s != null)
{
Console.WriteLine(s.Length);
}
特徴
- 型チェックとキャストが分離している
- null チェックが必須
- 同じ変数名を2回書くことが多い
今どきの書き方①:is で変数が生える
if (obj is string s)
{
Console.WriteLine(s.Length);
}
これだけです。
ポイント
-
isが 型チェック + 代入 を兼ねる -
sはキャスト済み - null も自動的に弾かれる
これは本当に便利。最初に感動したポイントです。
今どきの書き方②:&& / || の代わりに and / or
さらに進むとこうなります。
if (obj is string s && s.Length > 0)
…ではなく、
if (obj is string s and { Length: > 0 })
何が起きているか
-
andはパターン同士の結合 -
{ Length: > 0 }はプロパティパターン
つまり:
「string であり、かつ Length > 0」
を宣言的に書いています。
今どきの書き方③:or / not も使える
if (obj is not string)
{
return;
}
または
if (obj is string or int)
{
// どちらか
}
特に重要なのが not
if (obj is not string s)
{
return;
}
このコード、直感的にどう見えるでしょうか?
「string じゃないなら s に入る?」
と思いがちですが、実際は逆です。
重要ポイント:not の後に入る変数は「否定されていない値」
if (obj is not string s)
{
return;
}
// ここで s は string
Console.WriteLine(s.Length);
何が起きているか
-
obj is not string sが false のとき - つまり
obj is string sが true - そのときだけ if を抜ける
結果:
sは「string のときだけ存在する変数」
になります。
これはかなりトリッキーです。
さらに驚き:if の右端で作った変数が外で使える
これも最初かなり違和感がありました。
if (obj is not string s)
{
return;
}
// ここで s が使える
Console.WriteLine(s.Length);
なぜこうなるのか
C# のパターン変数は:
「条件が成立する経路でのみ有効」
というスコープを持っています。
つまりこの場合:
-
returnによって「失敗パス」が消える - 残るのは「成功パス(string)」だけ
結果として:
if の外なのに、変数が安全に使える
昔との対比
昔
if (!(obj is string))
{
return;
}
var s = (string)obj;
今
if (obj is not string s)
{
return;
}
かなりスッキリしました。
ちょっと混乱するポイント
正直、以下は慣れるまで違和感がありました。
① 記号と単語が混在している
&& → and
|| → or
! → not
- 論理演算子は従来通り使える
- でもパターンでは別の記法
「どっちを使うのが正しいのか?」と迷う
② 読み方が直感とズレることがある
if (obj is not string s)
これは
「string じゃないなら s」
ではなく
「string でないなら脱出。残りは string」
という意味になります。
③ 変数のスコープが“制御フロー依存”
if (obj is string s)
{
return;
}
// ここでは s は使えない
if (obj is not string s)
{
return;
}
// ここでは s が使える
この差はかなり重要です。
さいごに
今どきの C# のパターンマッチングは、
- 型チェック
- null チェック
- プロパティ検証
- 変数定義
をすべて一箇所にまとめられる強力な仕組みです。
ただし、
-
notの挙動 - スコープの決まり方
- 記法の多さ
など、最初は戸惑うポイントも多いです。
昔の C# は:
「手続き的に安全に書く言語」
でしたが、
今の C# は:
「条件を宣言的に記述する言語」
にかなり近づいています。
いかがだったでしょうか。
ご意見ご感想などありましたら、いただけると作者は喜びます。