C#は進化の速い言語であり、かつては単純な型チェックのためのツールに過ぎなかったis演算子は、現在では「パターンマッチング」という強力な機能を支える中心的な存在へと変貌を遂げました。
オブジェクトが特定の型であるかを確認するだけでなく、その内部の状態やプロパティの値を同時に判定し、さらには変数への代入までを一行で完結させることができます。
この記事では、C#のis演算子を用いた型チェックの基本から、最新のパターンマッチング機能までを、具体的なコード例と図解を交えて徹底的に解説します。
C#におけるis演算子の進化と役割
C#の初期から存在するis演算子ですが、バージョンアップを重ねるごとにその役割は大きく拡張されてきました。
現代のC#プログラミングにおいて、is演算子を正しく理解することは、コードの可読性と安全性を高めるための必須条件と言っても過言ではありません。

従来の型チェックとパターンマッチングの違い
C# 6.0以前の古いスタイルでは、オブジェクトの型を確認して利用するために、一度as演算子でキャストを試み、その結果がnullでないかを確認するという二段構えの手順が必要でした。
しかし、C# 7.0以降で導入された型パターンを使用すると、これらの操作を一つの式で記述できます。
従来の書き方(as演算子を利用)
従来の書き方では、キャストとnullチェックが分離しているため、コードが冗長になりがちでした。
public void ProcessDataOld(object data)
{
// 一旦asでキャストを試みる
string text = data as string;
// nullチェックを行ってから利用する
if (text != null)
{
Console.WriteLine("データの長さ: " + text.Length);
}
}
現代の書き方(is演算子の型パターン)
現代のC#では、is演算子の直後に型名と変数名を記述することで、型チェックと変数宣言を同時に行うことができます。
public void ProcessDataModern(object data)
{
// 型チェックと同時に変数「s」を宣言し、値を代入する
if (data is string s)
{
// このブロック内では変数sがstring型として利用可能
Console.WriteLine($"データの長さ: {s.Length}");
}
}
is演算子を使用するメリット
is演算子によるパターンマッチングを使用することには、単にコードが短くなる以上のメリットがあります。
| 特徴 | 詳細 |
|---|---|
| 安全性 | 明示的なキャスト( (T)obj )と違い、型が不一致でも例外が発生せず、falseを返すだけなので安全です。 |
| スコープの限定 | 宣言された変数は、if文のブロック内などの適切なスコープに限定されるため、変数の汚染を防げます。 |
| 可読性 | 「もしこれが~であれば」という条件を自然な英語に近い形で記述できるため、意図が伝わりやすくなります。 |
さまざまなパターンマッチングの活用
C#のis演算子は、単純な型チェック以外にも多様な「パターン」を判定できます。
これらを組み合わせることで、複雑な条件分岐を驚くほどシンプルに記述することが可能になります。

定数パターンとnullチェック
最も基本的なパターンの一つが、特定の定数と比較する定数パターンです。
特に、nullとの比較において、is演算子を使用することは現代のC#において強く推奨されています。
nullチェックにおけるisの優位性
従来の== nullは、演算子のオーバーロード(operator overloading)によって挙動が変更されている可能性があります。
しかし、is nullは演算子のオーバーロードを無視して厳密にnullかどうかを判定します。
object? myObj = null;
// 推奨されるnullチェック
if (myObj is null)
{
Console.WriteLine("オブジェクトはnullです");
}
// 推奨される非nullチェック(C# 9.0以降のnotパターン)
if (myObj is not null)
{
Console.WriteLine("オブジェクトはnullではありません");
}
リレーショナルパターン(比較演算)
C# 9.0から導入されたリレーショナルパターンでは、数値などの大小比較をis演算子で行うことができます。
これにより、範囲指定の条件分岐が非常に読みやすくなります。
public string GetTemperatureCategory(int temp) => temp switch
{
is < 0 => "氷点下",
is >= 0 and < 20 => "涼しい",
is >= 20 and < 30 => "暖かい",
is >= 30 => "暑い",
_ => "不明"
};
// if文での利用例
int score = 85;
if (score is >= 80)
{
Console.WriteLine("合格点です");
}
ロジカルパターン(論理結合)
and、or、notという論理演算子を組み合わせることで、複数の条件を合成したロジカルパターンを作成できます。
これらは従来の&&や||に似ていますが、パターンマッチングの文脈でより直感的に記述できます。
object input = 15;
if (input is int n and (> 10 or < 0))
{
Console.WriteLine("10より大きい、または0より小さい整数です。");
}
プロパティパターンによる高度な判定
is演算子の真骨頂とも言えるのが、プロパティパターンです。
これは、対象のオブジェクトの型をチェックすると同時に、その内部にあるプロパティの値が特定の条件を満たしているかを一度に確認できる機能です。

ネストされたプロパティのチェック
以前のC#では、深い階層にあるプロパティをチェックする場合、各階層でnullチェックを行う必要がありました。
プロパティパターンを使用すれば、途中のnullチェックをコンパイラに任せつつ、簡潔に記述できます。
public class Address { public string City { get; set; } = ""; }
public class Person { public string Name { get; set; } = ""; public int Age { get; set; } public Address? Home { get; set; } }
public void CheckTokyoResident(object obj)
{
// Person型であり、かつAgeが18以上、かつHome.Cityが"Tokyo"であるかを判定
if (obj is Person { Age: >= 18, Home: { City: "Tokyo" } } person)
{
Console.WriteLine($"{person.Name}さんは東京在住の成人です。");
}
}
このコードでは、もしHomeプロパティがnullであったとしても、条件全体がfalseになるだけで、NullReferenceExceptionは発生しません。
拡張プロパティパターン(C# 10以降)
C# 10.0からは、さらに簡潔に記述できるようになりました。
中括弧をネストさせる代わりに、ドット(.)を使って深いプロパティにアクセスできます。
// C# 10.0以降の書き方
if (obj is Person { Age: >= 18, Home.City: "Tokyo" } person)
{
// 処理
}
リストパターンによるコレクションの判定
C# 11.0で導入されたリストパターンにより、配列やリストの内容をis演算子で判定できるようになりました。
これはデータの順序や要素数、特定の要素の値をチェックする際に非常に強力です。

リストパターンの基本例
リストパターンを使うと、データの構造そのものを視覚的に表現するように記述できます。
int[] numbers = { 1, 2, 3, 4, 5 };
// 1で始まり、最後に5が含まれる、任意の長さの配列
if (numbers is [1, .., 5])
{
Console.WriteLine("最初が1、最後が5の配列です。");
}
// 要素数がちょうど3つの配列
if (numbers is [_, _, _])
{
Console.WriteLine("この配列の要素数は3です。");
}
スライスパターンと変数への代入
リスト内の特定の部分を別の変数として取り出すことも可能です。
int[] scores = { 100, 90, 80, 70, 60 };
if (scores is [int first, .. int[] middle, int last])
{
Console.WriteLine($"最初の要素: {first}");
Console.WriteLine($"最後の要素: {last}");
Console.WriteLine($"中間の要素数: {middle.Length}");
}
is演算子の実戦的なユースケース
ここからは、実際のアプリケーション開発において、どのようにis演算子とパターンマッチングを活用すべきか、具体的なシナリオを見ていきましょう。
JSONレスポンスの処理
動的なデータ構造(例えばobjectやJsonElement)を扱う際、パターンマッチングは真価を発揮します。
using System;
public class Program
{
public static void Main()
{
ProcessResponse(new { status = "success", code = 200 });
ProcessResponse(new { status = "error", message = "Unauthorized" });
}
public static void ProcessResponse(object response)
{
// プロパティの有無と値を同時にチェック
if (response is { status: "success", code: int c })
{
Console.WriteLine($"成功しました。ステータスコード: {c}");
}
else if (response is { status: "error", message: string msg })
{
Console.WriteLine($"エラーが発生しました: {msg}");
}
}
}
成功しました。ステータスコード: 200
エラーが発生しました: Unauthorized
インターフェースと型チェックの組み合わせ
プラグインシステムやイベントハンドラにおいて、特定のインターフェースを実装しているかどうかを確認しつつ、固有のプロパティにアクセスする場合にも便利です。
public interface IMessage { string Content { get; } }
public class TextMessage : IMessage { public string Content { get; set; } = ""; }
public class ImageMessage : IMessage { public string Content { get; set; } = ""; public string Url { get; set; } = ""; }
public void DisplayMessage(IMessage message)
{
// ImageMessage型の場合のみ、Urlプロパティにアクセスしたい
if (message is ImageMessage { Url: var url })
{
Console.WriteLine($"画像のURLを表示: {url}");
}
else
{
Console.WriteLine($"テキスト内容を表示: {message.Content}");
}
}
パフォーマンスに関する考察
is演算子によるパターンマッチングは、内部的にはコンパイラによって最適化されたif文や一時変数へと展開されます。
そのため、手動でキャストを行う場合と比較してもパフォーマンス上のデメリットはほぼありません。
むしろ、冗長なチェックを排除できるため、効率的なコードになることが多いです。
まとめ
C#のis演算子は、単なる型チェックの枠を超え、オブジェクトの構造や値を宣言的に判定するためのモダンな言語機能の中心となりました。
定数パターンによる安全なnullチェック、リレーショナルパターンによる直感的な範囲比較、そしてプロパティパターンやリストパターンによる高度なデータ抽出まで、その用途は多岐にわたります。
これらの機能を使いこなすことで、深くネストされたif文を排除し、バグが入り込みにくく、かつ意図が明確なコードを記述できるようになります。
最新のC#のパラダイムに則り、as演算子や古いキャスト手法の代わりに、積極的なパターンマッチングの活用を検討してみてください。
日常的なコーディングがよりシンプルで楽しいものへと変わるはずです。
