is / as / パターンマッチ
| 対応: | is / as | C# 1.0(2002) |
|---|---|---|
| Pattern Matching | C# 7.0(2017) |
オブジェクトの型を判定する『is』と安全にキャストする『as』、C# 8 以降で強化されたパターンマッチング(switch 式・when ガード)です。
構文
// 型を判定します(true/false)。
bool result = obj is Type;
// 型を判定して変数に代入します(パターン変数)。
if (obj is Type variable) { ... }
// 安全なキャストです(失敗すると null を返します)。
Type variable = obj as Type;
// switch 式によるパターンマッチングです。
string result = variable switch {
Type1 v => ...,
Type2 v when condition => ...,
_ => defaultValue
};
構文一覧
| 構文 | 概要 |
|---|---|
| obj is 型 | obj が指定した型かどうかを bool で返します。null には false を返します。 |
| obj is 型 v | 型が一致すると変数 v に代入します(型パターン変数)。C# 7 以降で使えます。 |
| obj as 型 | 型変換を試みます。失敗すると例外の代わりに null を返します。値型には使えません。 |
| switch 式 | C# 8 以降。各ケースをアロー(=>)で書き、値を返す式として使います。 |
| when ガード | switch の case に追加条件を付けます。 |
サンプルコード
Program.cs
using System;
// is で型判定します。
object value = "Hello";
if (value is string text)
{
Console.WriteLine($"文字列: {text.ToUpper()}"); // HELLO
}
// as で安全にキャストします。
object number = 42;
string str = number as string; // 失敗してもクラッシュしません。
Console.WriteLine(str == null ? "変換失敗" : str); // 変換失敗
// switch 式でパターンマッチングします。
object[] testData = { 42, "abc", 3.14, true, null };
foreach (object item in testData)
{
string description = item switch
{
int n when n > 100 => $"大きな整数: {n}",
int n => $"整数: {n}",
string s => $"文字列: {s}",
double d => $"小数: {d}",
bool b => $"真偽値: {b}",
null => "null です",
_ => "不明な型"
};
Console.WriteLine(description);
}
dotnet script is_as_pattern_match.csx 文字列: HELLO 変換失敗 整数: 42 文字列: abc 小数: 3.14 真偽値: True null です
実践パターン: クラス階層のディスパッチ
基底クラスのコレクションに格納された派生クラスを、パターンマッチングで型ごとに処理するパターンです。
PatternDispatch.cs
using System;
using System.Collections.Generic;
// メンバーの基底クラスです。
abstract class Member
{
public string Name { get; init; }
}
class Worker : Member
{
public int Score { get; init; }
}
class Client : Member
{
public bool HasFlag { get; init; }
}
class External : Member
{
public int RemainingQuota { get; init; }
}
// メンバーリストを作成します。
List<Member> members = new()
{
new Worker { Name = "user_1", Score = 210 },
new Client { Name = "user_2", HasFlag = true },
new External { Name = "user_3", RemainingQuota = 99999 },
new Worker { Name = "user_4", Score = 190 },
new Client { Name = "user_5", HasFlag = true },
};
// パターンマッチングで型ごとに処理します。
foreach (Member m in members)
{
string info = m switch
{
Worker w when w.Score >= 200 => $"[上級/担当者] {w.Name} (Score: {w.Score})",
Worker w => $"[担当者] {w.Name} (Score: {w.Score})",
Client cl when cl.HasFlag => $"[顧客/フラグあり] {cl.Name}",
Client cl => $"[顧客] {cl.Name}",
External ex => $"[外部] {ex.Name} (残枠: {ex.RemainingQuota})",
_ => $"[不明] {m.Name}"
};
Console.WriteLine(info);
}
dotnet script pattern_dispatch.csx [上級/担当者] user_1 (Score: 210) [顧客/フラグあり] user_2 [外部] user_3 (残枠: 99999) [担当者] user_4 (Score: 190) [顧客/フラグあり] user_5
実践パターン: プロパティパターン
C# 8 以降のプロパティパターンを使うと、オブジェクトのプロパティ値に基づいてマッチングできます。
PropertyPattern.cs
using System;
record Record(string Owner, bool Active, int PageCount);
// レコードの状態に応じてメッセージを返します。
static string DescribeRecord(Record record) => record switch
{
{ Owner: "user_1", Active: true } => "メインレコード: 使用中(所有者: user_1)",
{ Owner: "user_2", PageCount: > 0 } => $"参照レコード: {record.PageCount}ページ",
{ Active: false } => $"{record.Owner}のレコード: 完了済み",
_ => $"{record.Owner}のレコード"
};
var records = new[]
{
new Record("user_1", true, 50),
new Record("user_2", true, 200),
new Record("user_3", false, 0),
new Record("user_4", true, 30),
};
foreach (var record in records)
Console.WriteLine(DescribeRecord(record));
dotnet script property_pattern.csx メインレコード: 使用中(所有者: user_1) 参照レコード: 200ページ user_3のレコード: 完了済み user_4のレコード
よくあるミス
よくあるミス1: as を使った後に null チェックをしない
『as』でキャスト失敗すると null を返しますが、その後に null チェックせずにメンバーアクセスすると NullReferenceException が発生します。
using System; object obj = 42; // NG: as で変換失敗した場合、str は null になります。 string str = obj as string; Console.WriteLine(str.Length); // NullReferenceException が発生します。
dotnet run Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
修正後は次の通りです。
using System;
object obj = 42;
// OK: is パターン変数を使うと変換失敗時に中の処理が実行されません。
if (obj is string str)
{
Console.WriteLine(str.Length);
}
else
{
Console.WriteLine("string ではありません。");
}
dotnet run string ではありません。
よくあるミス2: as を値型に使う
『as』は参照型にしか使えません。値型(int・struct など)に使うとコンパイルエラーになります。
using System;
object obj = 42;
// NG: int は値型なので as は使えません(コンパイルエラー)。
// int n = obj as int; // error CS0077: The as operator must be used with a reference type
// OK: is でキャストします。
if (obj is int n)
Console.WriteLine($"int: {n}");
// OK: 明示的なキャストを使います(失敗時は InvalidCastException)。
int m = (int)obj;
Console.WriteLine($"int: {m}");
dotnet run int: 42 int: 42
概要
C# 7 以降の『is』は型チェックと変数代入を同時に行えるため、is で判定した後に別途キャストする書き方は不要です。if (obj is string s) のように一行で書けます。
『as』は参照型のキャストに使えますが、値型(int・struct など)には使えません。キャスト失敗が確認できないまま使うとその後の null 参照で例外になります。必ず null チェックを行ってください。
null のデフォルト値設定は『null 合体演算子 ?? / ??=』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。