C#のis演算子とは?型判定・パターンマッチングの使い方を初心者向けに解説
はじめに
C#でオブジェクトの型を確認したいときによく使うのが、is演算子です。c# isで調べている方の多くは、「この変数がstringかどうか確認したい」「object型の中身を安全に取り出したい」「asやキャストと何が違うのか知りたい」と感じているのではないでしょうか。
is演算子は、単なる型判定だけでなく、現在のC#ではパターンマッチングにも使われます。たとえば、型を判定しながら変数に代入したり、数値の範囲を判定したり、オブジェクトのプロパティの値まで見て条件分岐したりできます。
この記事では、C#のis演算子の基本から、is not、and、or、as演算子やtypeofとの違い、実践的なコード例まで初心者向けに解説します。
1. C#のis演算子とは?初心者向けに基本を解説
1-1. is演算子は「型判定」と「パターンマッチング」に使う演算子
C#のis演算子は、式の実行時の型が指定した型と互換性があるかを判定する演算子です。また、isはパターンに対して値が一致するかどうかを調べる用途にも使えます。MicrosoftのC#リファレンスでも、is演算子は「実行時の型が指定した型と互換性があるかを確認する」「式の結果をパターンに対してテストする」と説明されています。
最も基本的な例は次のとおりです。
C#object value = "Hello";
if (value is string)
{
Console.WriteLine("valueはstring型です");
}
このコードでは、valueの実行時の型がstringかどうかを確認しています。valueはobject型として宣言されていますが、実際に入っている値は文字列なので、value is stringはtrueになります。
1-2. is演算子の基本構文
is演算子の基本構文は次のとおりです。
C#式 is 型
または、パターンマッチングを使う場合は次のように書きます。
C#式 is パターン
型だけを判定する場合の例です。
C#object value = 123;
bool result = value is int;
Console.WriteLine(result); // True
型判定と同時に変数へ代入する場合は、次のように書きます。
C#object value = "C#";
if (value is string text)
{
Console.WriteLine(text.Length);
}
value is string textは、「valueがstringなら、その値をtextという変数として使えるようにする」という意味です。これを宣言パターンと呼びます。
1-3. is演算子がtrueになる条件
is演算子がtrueになる代表的な条件は、次のような場合です。
まず、実行時の型が指定した型そのものの場合です。
C#object value = "Hello";
Console.WriteLine(value is string); // True
次に、継承関係がある場合です。
C#class Animal { }
class Dog : Animal { }
object value = new Dog();
Console.WriteLine(value is Dog); // True
Console.WriteLine(value is Animal); // True
DogはAnimalを継承しているため、DogのインスタンスはAnimalとしても扱えます。そのため、value is Animalもtrueになります。
インターフェースを実装している場合もtrueになります。
C#using System.Collections.Generic;
object value = new List<int>();
Console.WriteLine(value is IEnumerable<int>); // True
List<int>はIEnumerable<int>を実装しているため、この判定はtrueです。
また、ボックス化された値型に対しても使えます。
C#object value = 10;
Console.WriteLine(value is int); // True
一方で、数値変換までは行われません。
C#object value = 10;
Console.WriteLine(value is int); // True
Console.WriteLine(value is long); // False
10は数値としてはlongに変換できますが、objectに入っている実際の型はintです。そのため、value is longはfalseになります。
1-4. is演算子はnullに対してどう動くのか
is演算子で型判定を行う場合、対象がnullなら基本的にfalseになります。
C#object value = null;
Console.WriteLine(value is string); // False
nullはstring型の変数に代入できますが、実行時の型を持つオブジェクトではありません。そのため、value is stringはfalseです。
ただし、パターンマッチングではnullチェックにis nullやis not nullを使えます。
C#object value = null;
if (value is null)
{
Console.WriteLine("valueはnullです");
}
非nullチェックは次のように書けます。
C#object value = "Hello";
if (value is not null)
{
Console.WriteLine("valueはnullではありません");
}
value != nullでも同じように見えますが、is nullはユーザー定義の==演算子の影響を受けにくいため、純粋なnull判定として使いやすい書き方です。
2. C#のis演算子で型判定を行う基本的な使い方
2-1. オブジェクトが特定の型か確認する
is演算子の最も基本的な使い方は、object型の値が特定の型かどうかを確認することです。
C#object value = "Hello C#";
if (value is string)
{
Console.WriteLine("文字列です");
}
else
{
Console.WriteLine("文字列ではありません");
}
object型は、文字列、数値、クラスのインスタンスなど、さまざまな値を受け取れます。そのため、実際に処理する前に型を確認したい場面でis演算子が役立ちます。
たとえば、値がintなら計算し、stringなら文字数を表示するような処理では次のように書けます。
C#object value = 100;
if (value is int)
{
Console.WriteLine("数値です");
}
else if (value is string)
{
Console.WriteLine("文字列です");
}
else
{
Console.WriteLine("その他の型です");
}
2-2. 継承関係にあるクラスをisで判定する
is演算子は、指定した型そのものだけでなく、継承元の型でも判定できます。
C#class Animal
{
public string Name { get; set; } = "";
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine("ワン!");
}
}
Animal animal = new Dog();
Console.WriteLine(animal is Animal); // True
Console.WriteLine(animal is Dog); // True
変数animalの宣言上の型はAnimalですが、実際に入っているインスタンスはDogです。そのため、animal is Dogはtrueになります。
このような判定は、基底クラスの変数に複数の派生クラスが入る可能性がある場合に便利です。
C#class Cat : Animal
{
public void Meow()
{
Console.WriteLine("ニャー");
}
}
Animal animal = new Cat();
if (animal is Dog)
{
Console.WriteLine("犬です");
}
else if (animal is Cat)
{
Console.WriteLine("猫です");
}
2-3. インターフェースを実装しているか判定する
is演算子は、あるオブジェクトが特定のインターフェースを実装しているかどうかも判定できます。
C#interface IPrintable
{
void Print();
}
class Report : IPrintable
{
public void Print()
{
Console.WriteLine("レポートを印刷します");
}
}
object document = new Report();
if (document is IPrintable)
{
Console.WriteLine("印刷可能です");
}
実際の処理では、型判定だけでなく、判定後にそのインターフェースのメソッドを呼び出したいことが多いです。その場合は、宣言パターンを使うと簡潔に書けます。
C#if (document is IPrintable printable)
{
printable.Print();
}
この書き方なら、documentがIPrintableを実装しているかを確認し、実装していればprintableとして安全に扱えます。
2-4. nullable型をisで判定する場合の注意点
nullable値型に対してisを使う場合は、少し注意が必要です。
C#int? number = 10;
if (number is int value)
{
Console.WriteLine(value); // 10
}
int?に値が入っている場合、number is int valueはtrueになり、valueには通常のintとして値が入ります。
一方、nullの場合はfalseです。
C#int? number = null;
if (number is int value)
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("値がありません");
}
このコードでは、numberに値がないためelse側が実行されます。
また、objectにnullable値型を代入する場合にも注意が必要です。
C#int? number = 10;
object value = number;
Console.WriteLine(value is int); // True
値を持つnullable値型は、objectに代入されると基になる値型としてボックス化されます。この例では、valueの実行時の型はNullable<int>ではなくintとして扱われます。
3. is演算子と型変換を組み合わせた使い方
3-1. 従来の「isで判定してからキャストする」書き方
以前からよく使われていた書き方に、「isで型を確認してからキャストする」という方法があります。
C#object value = "Hello";
if (value is string)
{
string text = (string)value;
Console.WriteLine(text.Length);
}
この書き方でも問題はありません。value is stringで文字列であることを確認しているため、その後の(string)valueは安全です。
ただし、同じ型名を2回書く必要があり、少し冗長です。現在のC#では、次に紹介する宣言パターンを使う方が一般的です。
3-2. 宣言パターンを使って型判定と変数代入を同時に行う
宣言パターンを使うと、型判定と変数代入を同時に行えます。
C#object value = "Hello";
if (value is string text)
{
Console.WriteLine(text.Length);
}
value is string textは、次の2つを同時に行っています。
valueがstringかどうかを判定すること。
stringだった場合に、textという変数に代入すること。
この書き方により、キャストの記述が不要になります。読みやすく、型変換ミスも起きにくくなります。
数値でも同じように使えます。
C#object value = 123;
if (value is int number)
{
Console.WriteLine(number * 2);
}
3-3. isを使うとInvalidCastExceptionを避けやすい理由
通常のキャストは、実行時に変換できない型へキャストしようとするとInvalidCastExceptionが発生することがあります。
C#object value = "Hello";
// 実行時エラーになる
int number = (int)value;
valueの中身はstringなので、intにはキャストできません。そのため、このコードは例外になります。
一方、isで判定してから処理すれば、型が合わない場合は処理をスキップできます。
C#object value = "Hello";
if (value is int number)
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("intではありません");
}
is演算子は「型が合う場合だけ変数として取り出す」という書き方ができるため、不要なキャスト例外を避けやすくなります。
3-4. if文でよく使う実践的なコード例
実際の開発では、引数として渡されたobject型の値を安全に処理する場面があります。
C#static void PrintValue(object value)
{
if (value is string text)
{
Console.WriteLine($"文字列: {text}");
}
else if (value is int number)
{
Console.WriteLine($"整数: {number}");
}
else if (value is DateTime date)
{
Console.WriteLine($"日付: {date:yyyy-MM-dd}");
}
else if (value is null)
{
Console.WriteLine("nullです");
}
else
{
Console.WriteLine("対応していない型です");
}
}
呼び出し例です。
C#PrintValue("C#");
PrintValue(100);
PrintValue(DateTime.Today);
PrintValue(null);
isを使うと、型ごとの処理を安全に分岐できます。特に、外部から受け取る値や、複数の型が混在する可能性のある値を扱うときに便利です。
4. C#のis演算子によるパターンマッチング
4-1. パターンマッチングとは何か
パターンマッチングとは、値が特定の形や条件に一致するかを調べる仕組みです。C#では、is式、switch文、switch式でパターンマッチングを使えます。対応しているパターンには、宣言パターン、型パターン、定数パターン、リレーショナルパターン、プロパティパターン、リストパターン、varパターン、破棄パターンなどがあります。
単純な型判定だけなら、次のように書けます。
C#if (value is string)
{
Console.WriteLine("文字列です");
}
パターンマッチングを使うと、型だけでなく値の条件も表現できます。
C#int score = 85;
if (score is >= 80)
{
Console.WriteLine("合格です");
}
さらに、複数条件を組み合わせることもできます。
C#if (score is >= 80 and <= 100)
{
Console.WriteLine("80点以上100点以下です");
}
4-2. 定数パターン:値が一致するか判定する
定数パターンは、値が特定の定数と一致するかを判定するパターンです。
C#int status = 200;
if (status is 200)
{
Console.WriteLine("成功です");
}
文字列にも使えます。
C#string role = "admin";
if (role is "admin")
{
Console.WriteLine("管理者です");
}
nullチェックにも定数パターンを使えます。
C#object value = null;
if (value is null)
{
Console.WriteLine("nullです");
}
is nullは、C#でよく使われる安全なnull判定の書き方です。
4-3. 型パターン:指定した型かどうかを判定する
型パターンは、指定した型に一致するかを判定するパターンです。
C#static string GetTypeName(object value)
{
return value switch
{
string => "文字列",
int => "整数",
DateTime => "日付",
null => "null",
_ => "その他"
};
}
この例では、switch式で型パターンを使っています。string、int、DateTimeのように型名だけを書くことで、その型かどうかを判定できます。
is式でも型パターンは使えます。
C#if (value is string)
{
Console.WriteLine("string型です");
}
ただし、実際にその値を変数として使いたい場合は、次のように宣言パターンを使う方が便利です。
C#if (value is string text)
{
Console.WriteLine(text.ToUpper());
}
4-4. varパターン:値を変数として受け取る
varパターンは、値を変数として受け取るパターンです。nullを含む任意の値に一致します。Microsoftのドキュメントでも、varパターンは式の結果を新しいローカル変数に割り当てる用途として説明されています。
C#int number = 10;
if (number is var value)
{
Console.WriteLine(value);
}
この例では、numberの値がvalueに入ります。ただし、このような単純な例ではあまり意味がありません。
varパターンは、中間結果を一度変数に受け取ってから条件判定したい場合に役立ちます。
C#static bool IsLongText(string text)
{
return text.Trim() is var trimmed
&& trimmed.Length >= 10;
}
このコードでは、text.Trim()の結果をtrimmedとして受け取り、その長さを判定しています。
4-5. 破棄パターン:どの値にも一致させる
破棄パターンは、_を使って「どの値にも一致する」ことを表すパターンです。主にswitch式の最後のデフォルト処理で使われます。
C#static string GetMessage(int statusCode)
{
return statusCode switch
{
200 => "OK",
404 => "Not Found",
500 => "Server Error",
_ => "Unknown"
};
}
_は、上のどの条件にも一致しなかった場合に使われます。
注意点として、is式で「何でも一致する」という意味で単独の_を使うのは避け、必要な場合はvar _を使います。
C#object value = "Hello";
if (value is var _)
{
Console.WriteLine("任意の値に一致します");
}
ただし、実務ではvalue is var _のような書き方をあえて使う場面は多くありません。破棄パターンは、switch式で漏れなく分岐を書くために使うことが多いです。
4-6. プロパティパターン:オブジェクトの中身で判定する
プロパティパターンは、オブジェクトのプロパティの値を見て判定するパターンです。
C#class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
public bool IsActive { get; set; }
}
User user = new User
{
Name = "Taro",
Age = 20,
IsActive = true
};
if (user is { Age: >= 18, IsActive: true })
{
Console.WriteLine("有効な成人ユーザーです");
}
このコードでは、userが次の条件を満たすかどうかを判定しています。
Ageが18以上であること。
IsActiveがtrueであること。
プロパティパターンを使うと、次のような複雑な条件も読みやすく書けます。
C#if (user is { Name.Length: > 0, Age: >= 18 and < 65 })
{
Console.WriteLine("名前があり、18歳以上65歳未満です");
}
通常のif文で書くと条件が長くなりがちですが、プロパティパターンを使うと「オブジェクトの形」として条件を表現できます。
4-7. リレーショナルパターン:数値の範囲を判定する
リレーショナルパターンは、<、>、<=、>=を使って値の範囲を判定するパターンです。
C#int score = 75;
if (score is >= 60)
{
Console.WriteLine("合格です");
}
andと組み合わせると範囲判定ができます。
C#if (score is >= 80 and <= 100)
{
Console.WriteLine("優秀です");
}
switch式でも便利です。
C#static string GetGrade(int score)
{
return score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
>= 0 => "F",
_ => "不正な点数"
};
}
この例では、上から順番に条件が評価されます。95なら最初の>= 90に一致し、"A"が返ります。
4-8. リストパターン:配列やリストの要素構造を判定する
リストパターンは、配列やリストの要素の並びに対してパターンマッチングを行う機能です。配列やリストを一連のパターンと照合し、各要素が対応するパターンに一致するかを判定できます。
C#int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers is [1, 2, 3]); // True
Console.WriteLine(numbers is [1, 2, 4]); // False
要素数も判定対象になります。
C#Console.WriteLine(numbers is [1, 2, 3, 4]); // False
_を使うと、特定の要素を無視できます。
C#if (numbers is [1, _, 3])
{
Console.WriteLine("最初が1、最後が3です");
}
..を使うと、途中の任意の要素を表せます。
C#int[] values = { 1, 2, 3, 4, 5 };
if (values is [1, .., 5])
{
Console.WriteLine("最初が1、最後が5です");
}
リストパターンは、コマンドライン引数、トークン列、CSVの分割結果など、要素の並びに意味があるデータを扱うときに便利です。
5. is not・and・orを使った条件判定
5-1. is notで「指定した型ではない」を判定する
is notを使うと、「指定した型ではない」「指定したパターンに一致しない」という条件を表現できます。
C#object value = 123;
if (value is not string)
{
Console.WriteLine("stringではありません");
}
nullでないことを確認する場合にもよく使います。
C#string? name = "Taro";
if (name is not null)
{
Console.WriteLine(name.Length);
}
is not nullは、読みやすく安全なnullチェックとしてよく使われます。
宣言パターンと組み合わせる場合は、次のように使えます。
C#object value = "Hello";
if (value is not string text)
{
Console.WriteLine("文字列ではありません");
}
else
{
Console.WriteLine(text.Length);
}
この場合、textはelse側で使えます。if側は「文字列ではない」場合なので、textは利用できません。
5-2. andで複数条件を組み合わせる
andを使うと、複数のパターンを同時に満たすかどうかを判定できます。
C#int age = 25;
if (age is >= 18 and < 65)
{
Console.WriteLine("成人で、65歳未満です");
}
通常の条件式で書くと次のようになります。
C#if (age >= 18 && age < 65)
{
Console.WriteLine("成人で、65歳未満です");
}
どちらでも書けますが、パターンマッチングでは「値がこの範囲に収まる」という意味を簡潔に表現できます。
プロパティパターンとも組み合わせられます。
C#if (user is { Age: >= 18 and < 65, IsActive: true })
{
Console.WriteLine("対象ユーザーです");
}
5-3. orで複数の候補に一致するか判定する
orを使うと、複数の候補のどれかに一致するかを判定できます。
C#int month = 1;
if (month is 12 or 1 or 2)
{
Console.WriteLine("冬です");
}
文字列にも使えます。
C#string role = "admin";
if (role is "admin" or "owner")
{
Console.WriteLine("管理権限があります");
}
switch式で使うと、似た条件をまとめられます。
C#static string GetSeason(int month)
{
return month switch
{
3 or 4 or 5 => "春",
6 or 7 or 8 => "夏",
9 or 10 or 11 => "秋",
12 or 1 or 2 => "冬",
_ => "不正な月"
};
}
5-4. not・and・orを組み合わせるときの優先順位
not、and、orを組み合わせる場合、優先順位に注意が必要です。C#のパターン結合子は、not、and、orの順に強く結合します。つまり、notが最も優先され、次にand、最後にorが評価されます。
たとえば、次のようなコードは意図が分かりにくくなります。
C#char c = 'A';
if (c is not >= 'a' and <= 'z')
{
Console.WriteLine("小文字ではない?");
}
このような場合は、括弧を使って意図を明確にしましょう。
C#if (c is not (>= 'a' and <= 'z'))
{
Console.WriteLine("小文字ではありません");
}
また、アルファベットかどうかを判定する場合は次のように書くと読みやすくなります。
C#if (c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z'))
{
Console.WriteLine("英字です");
}
5-5. 読みやすい条件式にするための書き方
is、not、and、orを使うと条件式を短く書けますが、短ければよいわけではありません。読みやすさを優先しましょう。
読みにくい例です。
C#if (value is not null and not "" and string)
{
Console.WriteLine("有効な文字列です");
}
このような場合は、宣言パターンを使って次のように書く方が自然です。
C#if (value is string text && text.Length > 0)
{
Console.WriteLine("有効な文字列です");
}
空白文字も除外したいなら、通常のメソッドを使う方が分かりやすいです。
C#if (value is string text && !string.IsNullOrWhiteSpace(text))
{
Console.WriteLine("有効な文字列です");
}
パターンマッチングは便利ですが、すべての条件を無理にisで表現する必要はありません。通常の&&、||、メソッド呼び出しと組み合わせることで、読みやすいコードになります。
6. is演算子・as演算子・typeof・GetTypeの違い
6-1. is演算子とas演算子の違い
is演算子は、型が一致するかを判定します。
C#object value = "Hello";
if (value is string)
{
Console.WriteLine("stringです");
}
一方、as演算子は、変換できる場合は指定した型として返し、変換できない場合はnullを返します。as演算子は、変換できない場合でもキャスト式のように例外を投げません。
C#object value = "Hello";
string? text = value as string;
if (text != null)
{
Console.WriteLine(text.Length);
}
現在のC#では、isの宣言パターンを使う方が簡潔なことが多いです。
C#if (value is string text)
{
Console.WriteLine(text.Length);
}
asは、変換結果を後で使いたい場合や、古いコードでよく見かける書き方です。ただし、変換後に必ずnullチェックが必要です。
6-2. is演算子とキャストの違い
キャストは、値を指定した型として扱うための構文です。
C#object value = "Hello";
string text = (string)value;
このコードは、valueの中身がstringなので成功します。
しかし、型が合わない場合は例外が発生します。
C#object value = 123;
string text = (string)value; // InvalidCastException
is演算子は、キャストできるかどうかを事前に判定できます。
C#object value = 123;
if (value is string text)
{
Console.WriteLine(text);
}
else
{
Console.WriteLine("stringではありません");
}
安全に型を確認しながら取り出したい場合は、isの宣言パターンが便利です。
6-3. is演算子とtypeofの違い
typeofは、型そのものを表すSystem.Typeオブジェクトを取得するための演算子です。typeofの引数には、変数ではなく型名を指定します。
C#Type type = typeof(string);
Console.WriteLine(type.FullName); // System.String
isは値に対する判定です。
C#object value = "Hello";
Console.WriteLine(value is string); // True
一方、typeofは型情報を取得するために使います。
C#Console.WriteLine(typeof(string) == typeof(string)); // True
実行時の型を比較したい場合は、GetType()とtypeofを組み合わせます。
C#object value = "Hello";
Console.WriteLine(value.GetType() == typeof(string)); // True
6-4. is演算子とGetTypeの違い
GetType()は、オブジェクトの実行時の正確な型を取得するメソッドです。
C#object value = "Hello";
Console.WriteLine(value.GetType()); // System.String
isは、継承関係やインターフェース実装も考慮して判定します。
C#class Animal { }
class Dog : Animal { }
object value = new Dog();
Console.WriteLine(value is Animal); // True
Console.WriteLine(value.GetType() == typeof(Animal)); // False
Console.WriteLine(value.GetType() == typeof(Dog)); // True
DogはAnimalを継承しているため、value is Animalはtrueです。しかし、実際の型はDogなので、value.GetType() == typeof(Animal)はfalseになります。
6-5. 継承関係まで判定したい場合に使う方法
継承関係やインターフェース実装まで含めて判定したい場合は、is演算子を使います。
C#if (value is Animal)
{
Console.WriteLine("Animalとして扱えます");
}
インターフェースでも同じです。
C#if (value is IDisposable disposable)
{
disposable.Dispose();
}
「この型として扱えるか」を知りたい場合は、isが向いています。
6-6. 厳密に同じ型か判定したい場合に使う方法
厳密に同じ型かどうかを判定したい場合は、GetType()とtypeofを使います。
C#if (value.GetType() == typeof(Dog))
{
Console.WriteLine("正確にDog型です");
}
ただし、valueがnullの場合にGetType()を呼ぶとNullReferenceExceptionになります。そのため、nullの可能性がある場合は先にチェックします。
C#if (value is not null && value.GetType() == typeof(Dog))
{
Console.WriteLine("正確にDog型です");
}
継承関係を含めたいならis、厳密に同じ型だけを判定したいならGetType() == typeof(...)と覚えると分かりやすいです。
7. C#のis演算子でよくあるエラー・注意点
7-1. nullをisで判定するときの注意点
value is stringのような型判定では、valueがnullの場合はfalseになります。
C#object value = null;
Console.WriteLine(value is string); // False
nullかどうかを判定したいなら、is nullまたはis not nullを使います。
C#if (value is null)
{
Console.WriteLine("nullです");
}
if (value is not null)
{
Console.WriteLine("nullではありません");
}
宣言パターンも、対象がnullなら一致しません。
C#object value = null;
if (value is string text)
{
Console.WriteLine(text);
}
else
{
Console.WriteLine("stringではない、またはnullです");
}
string textにはnullが入りません。valueがnullの場合はelse側に進みます。
7-2. ユーザー定義変換はis演算子の判定対象にならない
is演算子は、ユーザー定義の変換演算子を考慮しません。Microsoftのリファレンスでも、is演算子はユーザー定義変換を考慮しないと説明されています。
たとえば、独自クラスから別の型への変換演算子を定義していても、isの型判定ではその変換は使われません。
C#class UserId
{
public int Value { get; }
public UserId(int value)
{
Value = value;
}
public static implicit operator int(UserId id)
{
return id.Value;
}
}
object value = new UserId(10);
Console.WriteLine(value is int); // False
UserIdからintへの暗黙的変換は定義されていますが、valueの実行時の型はUserIdです。そのため、value is intはfalseになります。
変換したい場合は、明示的に変換処理を呼び出しましょう。
7-3. dynamic型をパターンマッチングで使うときの注意点
dynamic型は、コンパイル時ではなく実行時にメンバー解決が行われます。そのため、isによる型判定自体は使えますが、判定後の扱いに注意が必要です。
C#dynamic value = "Hello";
if (value is string text)
{
Console.WriteLine(text.Length);
}
このコードは問題なく動作します。valueがstringならtextとして扱えます。
ただし、dynamicのままメンバーにアクセスすると、存在しないメンバーでもコンパイル時に検出されにくくなります。
C#dynamic value = "Hello";
// 実行時エラーになる可能性がある
Console.WriteLine(value.UnknownMethod());
dynamicを使う場合でも、isで具体的な型に絞り込んでから処理すると安全性が高まります。
C#if (value is string text)
{
Console.WriteLine(text.ToUpper());
}
7-4. orやnotパターン内で変数宣言できないケース
orパターンやnotパターンでは、変数宣言の扱いに注意が必要です。
たとえば、次のようなコードは意図どおりに書けません。
C#// コンパイルエラーになる例
if (value is string text or int number)
{
Console.WriteLine("stringまたはintです");
}
orの左右で別々の変数を宣言すると、その後のスコープでどの変数が使えるのか曖昧になります。そのため、C#ではこのような書き方は制限されます。
この場合は、条件を分ける方が分かりやすいです。
C#if (value is string text)
{
Console.WriteLine($"文字列: {text}");
}
else if (value is int number)
{
Console.WriteLine($"整数: {number}");
}
または、switch式やswitch文を使うと整理しやすくなります。
C#switch (value)
{
case string text:
Console.WriteLine($"文字列: {text}");
break;
case int number:
Console.WriteLine($"整数: {number}");
break;
}
7-5. リレーショナルパターンが使える型・使えない型
リレーショナルパターンは、<、>、<=、>=で比較できる値に使います。代表的には数値型、char、列挙型などです。
C#int score = 80;
if (score is >= 60)
{
Console.WriteLine("合格です");
}
charにも使えます。
C#char c = 'g';
if (c is >= 'a' and <= 'z')
{
Console.WriteLine("小文字です");
}
一方で、任意のオブジェクトや複雑なクラスに対して、いきなりリレーショナルパターンを使えるわけではありません。
C#object value = new object();
// このような比較はできない
// if (value is > 10) { }
比較したい場合は、まず型を絞り込んでから条件を判定します。
C#object value = 15;
if (value is int number && number > 10)
{
Console.WriteLine("10より大きい整数です");
}
7-6. 条件が複雑になりすぎる場合のリファクタリング方法
isやパターンマッチングは便利ですが、条件を詰め込みすぎると読みにくくなります。
読みにくい例です。
C#if (user is { Age: >= 18 and < 65, IsActive: true, Name.Length: > 0 }
&& user.Name.StartsWith("A")
&& user.Name.Contains("x") is false)
{
Console.WriteLine("対象ユーザーです");
}
このような場合は、条件をメソッドに分けると読みやすくなります。
C#static bool IsTargetUser(User user)
{
return user is { Age: >= 18 and < 65, IsActive: true, Name.Length: > 0 }
&& user.Name.StartsWith("A")
&& !user.Name.Contains("x");
}
if (IsTargetUser(user))
{
Console.WriteLine("対象ユーザーです");
}
さらに条件が増える場合は、ドメインロジックとしてクラス内にメソッドを持たせることも検討します。
C#class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
public bool IsActive { get; set; }
public bool CanUseService()
{
return Age >= 18 && IsActive && !string.IsNullOrWhiteSpace(Name);
}
}
isで何でも解決しようとせず、読みやすさと責務の分離を意識することが大切です。
8. is演算子を使う実践例
8-1. object型の値を安全に処理する例
object型の値を受け取り、型ごとに処理を変える例です。
C#static void PrintObject(object? value)
{
if (value is string text)
{
Console.WriteLine($"文字列: {text}");
}
else if (value is int number)
{
Console.WriteLine($"整数: {number}");
}
else if (value is double doubleValue)
{
Console.WriteLine($"小数: {doubleValue}");
}
else if (value is null)
{
Console.WriteLine("nullです");
}
else
{
Console.WriteLine($"その他の型: {value.GetType().Name}");
}
}
呼び出し例です。
C#PrintObject("Hello");
PrintObject(123);
PrintObject(3.14);
PrintObject(null);
PrintObject(DateTime.Now);
isを使うことで、キャスト例外を避けながら型ごとの処理を書けます。
8-2. 例外処理で例外の型を判定する例
例外処理では、発生した例外の型によって処理を変えたい場合があります。
C#try
{
int number = int.Parse("abc");
}
catch (Exception ex)
{
if (ex is FormatException)
{
Console.WriteLine("数値の形式が正しくありません");
}
else if (ex is OverflowException)
{
Console.WriteLine("数値が大きすぎます");
}
else
{
Console.WriteLine("その他のエラーです");
}
}
ただし、例外型ごとに分けるだけなら、通常は複数のcatchを書く方が自然です。
C#try
{
int number = int.Parse("abc");
}
catch (FormatException)
{
Console.WriteLine("数値の形式が正しくありません");
}
catch (OverflowException)
{
Console.WriteLine("数値が大きすぎます");
}
isが向いているのは、すでにException型として受け取った値を後から分類したい場合です。
C#static string GetErrorMessage(Exception ex)
{
return ex switch
{
FormatException => "形式エラーです",
OverflowException => "範囲外エラーです",
ArgumentNullException => "引数がnullです",
_ => "不明なエラーです"
};
}
8-3. コレクション内の要素を型ごとに処理する例
複数の型が混ざったコレクションを処理する場合にもisが使えます。
C#var items = new object[]
{
"C#",
100,
DateTime.Today,
3.14,
null
};
foreach (var item in items)
{
if (item is string text)
{
Console.WriteLine($"文字列: {text}");
}
else if (item is int number)
{
Console.WriteLine($"整数: {number}");
}
else if (item is DateTime date)
{
Console.WriteLine($"日付: {date:yyyy-MM-dd}");
}
else if (item is null)
{
Console.WriteLine("null");
}
else
{
Console.WriteLine("その他");
}
}
switch式を使うと、さらに整理できます。
C#foreach (var item in items)
{
string message = item switch
{
string text => $"文字列: {text}",
int number => $"整数: {number}",
DateTime date => $"日付: {date:yyyy-MM-dd}",
null => "null",
_ => "その他"
};
Console.WriteLine(message);
}
8-4. WebアプリやAPIで入力値を判定する例
WebアプリやAPIでは、外部から受け取った入力値を検証することが重要です。たとえば、objectとして受け取った値が有効な文字列かどうかを確認する場合は、次のように書けます。
C#static bool IsValidName(object? input)
{
return input is string name
&& !string.IsNullOrWhiteSpace(name)
&& name.Length <= 50;
}
年齢の入力を判定する例です。
C#static bool IsValidAge(object? input)
{
return input is int age
&& age is >= 0 and <= 120;
}
リクエストモデルのプロパティを判定する場合は、プロパティパターンが使えます。
C#class RegisterRequest
{
public string? Name { get; set; }
public int Age { get; set; }
}
static bool IsValidRequest(RegisterRequest? request)
{
return request is
{
Name.Length: > 0 and <= 50,
Age: >= 0 and <= 120
};
}
ただし、実際のWebアプリでは、フレームワークのバリデーション機能や属性、専用のバリデーションライブラリを使う方が適切な場合もあります。isは簡単な条件分岐や補助的な判定に向いています。
8-5. switch式と組み合わせて分岐を整理する例
is演算子と同じパターンマッチングの考え方は、switch式でも使えます。複数の型や条件を分岐する場合は、if文を並べるよりもswitch式の方が読みやすいことがあります。
C#static string Describe(object? value)
{
return value switch
{
null => "nullです",
string { Length: 0 } => "空文字です",
string text => $"文字列: {text}",
int number when number >= 0 => $"正の整数: {number}",
int number => $"負の整数: {number}",
DateTime date => $"日付: {date:yyyy-MM-dd}",
_ => "その他の値です"
};
}
この例では、型判定、プロパティパターン、when条件を組み合わせています。
switch式は、値を返す分岐に向いています。一方、処理内容が複雑で副作用が多い場合は、通常のif文やswitch文の方が読みやすいこともあります。
9. C#のis演算子を使うべき場面・避けるべき場面
9-1. is演算子が向いている場面
is演算子が向いているのは、型が不確定な値を安全に扱いたい場面です。
たとえば、object型の値を受け取る場合です。
C#static void Handle(object? value)
{
if (value is string text)
{
Console.WriteLine(text);
}
}
複数の派生クラスを基底クラスとして受け取る場合にも便利です。
C#Animal animal = GetAnimal();
if (animal is Dog dog)
{
dog.Bark();
}
また、nullチェックにも向いています。
C#if (value is not null)
{
Console.WriteLine(value.ToString());
}
数値の範囲判定やプロパティの値による判定にも使えます。
C#if (score is >= 80 and <= 100)
{
Console.WriteLine("高得点です");
}
9-2. ポリモーフィズムで解決した方がよい場面
isで型判定を多用している場合、ポリモーフィズムで解決できないかを考えるべきです。
たとえば、次のようなコードです。
C#static void MakeSound(Animal animal)
{
if (animal is Dog)
{
Console.WriteLine("ワン");
}
else if (animal is Cat)
{
Console.WriteLine("ニャー");
}
}
このような処理は、各クラスにメソッドを持たせた方が自然です。
C#abstract class Animal
{
public abstract void MakeSound();
}
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("ワン");
}
}
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("ニャー");
}
}
呼び出し側は型判定をしなくて済みます。
C#static void MakeSound(Animal animal)
{
animal.MakeSound();
}
型ごとの振る舞いがオブジェクト自身に属するなら、isで分岐するよりもポリモーフィズムを使う方が保守しやすくなります。
9-3. 複雑な型判定が増えたときに見直すポイント
次のような状態になったら、設計を見直すサインです。
同じ型判定が複数の場所に出てくる。
if (x is A)、else if (x is B)が長く続く。
新しい型を追加するたびに、複数の分岐を修正する必要がある。
パターンマッチングの条件が複雑すぎて読みにくい。
たとえば、次のようなコードがあちこちにある場合です。
C#if (shape is Circle circle)
{
// 円の処理
}
else if (shape is Rectangle rectangle)
{
// 長方形の処理
}
else if (shape is Triangle triangle)
{
// 三角形の処理
}
面積を求める処理であれば、各クラスにGetArea()を実装した方がよいでしょう。
C#abstract class Shape
{
public abstract double GetArea();
}
一方で、ログ出力や一時的な変換処理など、オブジェクト自身の責務ではない処理なら、switch式やisで分岐するのが適切な場合もあります。
9-4. 保守しやすいコードにするための判断基準
isを使うかどうか迷ったときは、次の基準で考えると判断しやすくなります。
「この型として扱えるか」を一時的に確認したいだけなら、isが向いています。
C#if (value is IDisposable disposable)
{
disposable.Dispose();
}
型ごとの振る舞いが本質的に異なるなら、ポリモーフィズムを検討します。
C#animal.MakeSound();
厳密な型一致が必要なら、GetType()とtypeofを使います。
C#if (value is not null && value.GetType() == typeof(Dog))
{
Console.WriteLine("正確にDog型です");
}
条件が複雑になりすぎるなら、メソッドに分けます。
C#if (IsValidUser(user))
{
Console.WriteLine("有効なユーザーです");
}
is演算子は強力ですが、使いどころを選ぶことで、読みやすく保守しやすいコードになります。
10. C#のis演算子に関するよくある質問
10-1. is演算子は値型にも使える?
はい、is演算子は値型にも使えます。
C#object value = 10;
if (value is int number)
{
Console.WriteLine(number);
}
この例では、intの値がobjectにボックス化されており、value is intはtrueになります。
nullable値型にも使えます。
C#int? value = 10;
if (value is int number)
{
Console.WriteLine(number);
}
ただし、int?がnullの場合は一致しません。
C#int? value = null;
Console.WriteLine(value is int); // False
10-2. is演算子でnullチェックはできる?
できます。is nullとis not nullを使います。
C#if (value is null)
{
Console.WriteLine("nullです");
}
C#if (value is not null)
{
Console.WriteLine("nullではありません");
}
型判定とnullチェックを同時に行いたい場合は、宣言パターンを使います。
C#if (value is string text)
{
Console.WriteLine(text.Length);
}
この場合、valueがnullなら条件はfalseになります。
10-3. isと==の違いは?
isは、型やパターンに一致するかを判定します。
C#object value = "Hello";
Console.WriteLine(value is string); // True
==は、値が等しいかを比較します。
C#string text = "Hello";
Console.WriteLine(text == "Hello"); // True
nullチェックでは、どちらも使えます。
C#if (value == null)
{
Console.WriteLine("nullです");
}
if (value is null)
{
Console.WriteLine("nullです");
}
ただし、==は型によって演算子がオーバーロードされている場合があります。純粋なnullチェックを明示したい場合は、is nullやis not nullが読みやすいことがあります。
10-4. isとswitch式はどちらを使うべき?
条件が1つか2つ程度なら、isを使ったif文が分かりやすいです。
C#if (value is string text)
{
Console.WriteLine(text);
}
複数の型や複数のパターンに分岐する場合は、switch式が向いています。
C#string result = value switch
{
string text => $"文字列: {text}",
int number => $"整数: {number}",
null => "null",
_ => "その他"
};
処理を実行したい場合はif文やswitch文、値を返したい場合はswitch式が使いやすいです。
10-5. is演算子はパフォーマンスに影響する?
通常のアプリケーション開発では、is演算子のパフォーマンスを過度に心配する必要はありません。型判定やパターンマッチングはC#で一般的に使われる機能であり、可読性や安全性のメリットが大きい場面が多いです。
ただし、非常に大量のデータを処理するループ内で複雑なパターンマッチングを何度も行う場合は、パフォーマンス計測を行う価値があります。
C#foreach (var item in largeItems)
{
if (item is string text)
{
// 大量に実行される処理
}
}
パフォーマンスが気になる場合でも、推測で最適化するのではなく、まず計測することが重要です。多くの場合は、isを避けるよりも、データ構造やアルゴリズムを見直す方が効果的です。
まとめ
C#のis演算子は、型判定とパターンマッチングに使う重要な演算子です。基本的には、値が特定の型として扱えるかどうかを確認するために使います。
C#if (value is string text)
{
Console.WriteLine(text);
}
このように宣言パターンを使うと、型判定と変数代入を同時に行えるため、キャストよりも安全で読みやすいコードになります。
また、is null、is not nullによるnullチェック、andやorを使った条件の組み合わせ、リレーショナルパターンによる範囲判定、プロパティパターンによるオブジェクトの中身の判定など、現在のC#ではis演算子の活用範囲が大きく広がっています。
一方で、isによる型判定を多用しすぎると、コードが複雑になったり、オブジェクト指向らしい設計から離れたりすることもあります。型ごとの振る舞いを分けたい場合は、ポリモーフィズムで解決できないかも検討しましょう。
「この値は指定した型として扱えるか」を安全に確認したいときはis。
「変換できれば値を取り出したい」ときはisの宣言パターン。
「厳密に同じ型か確認したい」ときはGetType()とtypeof。
このように使い分けることで、C#の型判定をより安全で読みやすく書けるようになります。

