1. is オペレーター: 型の互換性チェック
実際のアプリだと、基底クラスの参照を持ってるけど、実際のオブジェクトはもっと具体的な派生型かも、ってことよくあるよね。そんな時、その派生型だけのメンバーにアクセスしたい場合もある。C#には、こういう時に便利なツールがいくつかあるんだ:is、as オペレーター、そして最近のバージョンで特に重要なパターンマッチング (pattern matching)。
is は、オブジェクトが指定した型、またはその派生型、あるいはインターフェースを実装しているかどうかをチェックできる。互換性があれば true、なければ false を返すよ。
主な使い方: オブジェクトを特定の型に安全にキャストできるかどうかを調べる。
例: 基底型と継承のチェック
class Animal { public string Species { get; set; } = "Unknown"; }
class Dog : Animal { public void Bark() => Console.WriteLine("Woof!"); }
class Cat : Animal { public void Meow() => Console.WriteLine("Meow!"); }
Animal myAnimal = new Dog { Species = "Golden Retriever" };
Console.WriteLine($"myAnimal is Animal: {myAnimal is Animal}"); // True
Console.WriteLine($"myAnimal is Dog: {myAnimal is Dog}"); // True
Console.WriteLine($"myAnimal is Cat: {myAnimal is Cat}"); // False
この例だと、myAnimal の実体は Dog だよ。だから myAnimal is Animal も myAnimal is Dog も true になる。
例: null のチェック
is オペレーターは null に対しても予測通りに動くよ。
Animal nullAnimal = null;
Dog specificDog = new Dog();
Console.WriteLine($"nullAnimal is Animal: {nullAnimal is Animal}"); // False (nullはどんな型のインスタンスでもない)
Console.WriteLine($"specificDog is null: {specificDog is null}"); // False (オブジェクトはnullじゃない)
null はどんな型のインスタンスでもないから、null is MyType は常に false になる。でも someObject is null って書くと、参照が null かどうか安全にチェックできるよ。
例: インターフェースで is を使う
is オペレーターはインターフェースの実装チェックにも使える。
interface IFlyable { void Fly(); }
class Bird : Animal, IFlyable { public void Fly() => Console.WriteLine("Flap flap!"); }
class Fish : Animal { }
Animal creature = new Bird();
Console.WriteLine($"creature is Bird: {creature is Bird}"); // True
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // True
creature = new Fish();
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // False
2. as オペレーター: 安全な型変換
as オペレーターは、オブジェクトを指定した型に安全にキャストするためのもの。明示的なキャスト (Type)obj だと失敗時に InvalidCastException が投げられるけど、as なら変換できなかった時 null を返す。実際の型が分からない時にピッタリ。
主な使い方: 型変換を試みて、失敗したら null を受け取る。
例: as の基本的な使い方
class Shape { }
class Circle : Shape { public double Radius { get; set; } }
class Square : Shape { public double Side { get; set; } }
Shape myShape = new Circle { Radius = 5.0 };
// Circleにキャストを試みる
Circle circle = myShape as Circle;
if (circle != null) // nullチェックは必須!
{
Console.WriteLine($"これは半径: {circle.Radius} の円だよ"); // 出力: これは半径: 5 の円だよ
}
// Squareにキャストを試みる
Square square = myShape as Square;
if (square == null) // キャスト失敗、square == null
{
Console.WriteLine("これは四角形じゃない。"); // 出力: これは四角形じゃない。
}
こんな感じで、as を使えば実行時エラーを避けて null で判定できる。
例: as の制限
as オペレーターは参照型と nullable な値型だけに使える。普通の値型には使えないよ、だって null を取れないからね。
object someValue = 100;
int num = someValue as int; // コンパイルエラー: 'as'はnull不可型には使えない
int? nullableNum = someValue as int?; // OK, nullable int
Console.WriteLine($"Nullable int: {nullableNum}"); // 出力: Nullable int: 100
string str = someValue as string; // 100はstringじゃないのでnullを返す
Console.WriteLine($"String from int: {str ?? "null"}"); // 出力: String from int: null
null不可の値型には、型が確実な場合は明示的キャスト ((int)someValue) を使うか、できれば pattern matching を使おう。
3. Pattern Matching (パターンマッチング)
pattern matching は、型チェックやデータ抽出をもっとエレガントかつ安全にできる強力な機能。冗長なコードを減らして読みやすくしてくれるよ。
Type Pattern (is を使った型パターン)
一番よく使うパターンで、型チェックと同時に新しい変数にキャストできる。
構文: 式 is 型 変数
例: is + キャストの置き換え
// Shape, Circle, Square クラスは前と同じ
Shape currentShape = new Circle { Radius = 7.5 };
// 古いやり方:
if (currentShape is Circle)
{
Circle c = (Circle)currentShape;
Console.WriteLine($"古いやり方: 半径{c.Radius}の円");
}
// 新しいエレガントなやり方 (type pattern)
if (currentShape is Circle c) // 型チェックと変数 'c' の作成
{
Console.WriteLine($"新しいやり方: 半径{c.Radius}の円"); // 'c' はすでにCircle型
}
Shape anotherShape = new Square { Side = 10.0 };
if (anotherShape is Square s)
{
Console.WriteLine($"これは一辺{s.Side}の四角形");
}
c や s は if ブロック内だけで使えるから、型が違う時にうっかり使う心配もないよ。
Property Pattern (プロパティパターン)
C# 8.0 からは、型チェックだけじゃなくて、オブジェクトのプロパティの値も同時にチェックしたり、新しい変数に抽出したりできるようになった。
構文: 式 is 型 { プロパティ1: 値パターン, プロパティ2: 抽出変数 }
例: プロパティのチェック
Shape testShape = new Circle { Color = "Green", Radius = 12.0 };
// オブジェクトがCircleで、色がグリーンかチェック
if (testShape is Circle { Color: "Green" })
{
Console.WriteLine("グリーンの円を見つけた。");
}
// オブジェクトがCircleで、半径を抽出
if (testShape is Circle { Radius: var r })
{
Console.WriteLine($"半径を抽出: {r}");
}
// チェックと抽出を組み合わせ
if (testShape is Circle { Color: "Green", Radius: var radiusVal } circleObj)
{
Console.WriteLine($"グリーンの円を抽出。半径: {radiusVal}, オブジェクト: {circleObj.Radius}");
}
プロパティパターンを使うと、複雑な条件分岐もすっきり書けるよ。
例: プロパティパターンで範囲や論理演算子を使う
プロパティの値が条件を満たすかどうかもチェックできるようになった!
Circle bigCircle = new Circle { Radius = 25.0, Color = "Blue" };
// 半径が20より大きくて色がブルー
if (bigCircle is Circle { Radius: > 20, Color: "Blue" })
{
Console.WriteLine("大きな青い円を発見。");
}
// 半径が5以上15以下
if (testShape is Circle { Radius: >= 5 and <= 15 })
{
Console.WriteLine("中くらいの円だよ。");
}
注意! bool 型とは違って、ここでは and、or、not ってキーワードを使うよ。
4. Switch Expressions と Switch Statements の Pattern Matching
pattern matching の真骨頂は switch で発揮される!パターンごとに違うロジックを実行できて、めっちゃ読みやすい。
例: Switch Statement
パターンごとにコードブロックを分けて書ける。
// Animal, Dog, Cat クラスは最初の例と同じ
Animal currentCreature = new Dog { Species = "Poodle" };
switch (currentCreature)
{
case Dog d: // 型パターン: Dogなら'd'に代入
d.Bark();
Console.WriteLine($"これは{d.Species}犬だよ。");
break;
case Cat c when c.Species == "Siamese": // 型パターン+条件
c.Meow();
Console.WriteLine($"これはシャム猫だよ。");
break;
case Animal a: // AnimalだけどDog/Catじゃない
Console.WriteLine($"これはただの{a.Species}動物。");
break;
case null: // nullパターン
Console.WriteLine("オブジェクトはnullだよ。");
break;
default: // どれにも当てはまらない
Console.WriteLine("未知の生き物。");
break;
}
switch のcaseの順番は大事!より具体的なパターンを先に書こう。
例: Switch Expression
switch のもっとコンパクトな書き方。型やプロパティに応じて値を返すのに最適!
構文: 式 switch { パターン1 => 結果1, パターン2 => 結果2, ... _ => デフォルト結果 }
Shape processShape = new Rectangle { Width = 5, Height = 5, Color = "Red" };
string shapeInfo = processShape switch
{
Circle { Radius: var r } when r > 10 => $"大きな円 (R={r})", // プロパティパターン+条件
Circle { Color: "Blue" } c => $"青い円 (R={c.Radius})", // プロパティパターン+抽出
Circle c => $"普通の円 (R={c.Radius})", // 型パターン
Rectangle { Width: var w, Height: var h } when w == h => $"正方形 ({w}x{h})", // 正方形
Rectangle r => $"長方形 ({r.Width}x{r.Height})", // その他の長方形
null => "図形が存在しない (null)", // nullパターン
_ => "未知の図形" // ディスカードパターン (_)
};
Console.WriteLine(shapeInfo); // 出力: 正方形 (5x5)
switch expression は短くて分かりやすい変換に超便利!
Var Pattern (var パターン)
C# 7.0 からは、pattern matching で var も使える。var は (null以外) どんなオブジェクトにもマッチして、その型で変数に入れてくれる。
例: var パターンの使い方
object obj = "Hello World";
if (obj is var result) // 常にtrue、resultはstring
{
Console.WriteLine($"型: {result.GetType().Name}, 値: {result}");
}
obj = 123;
string typeName = obj switch
{
var x when x is int => "これは整数",
var y when y is string => "これは文字列",
_ => "その他"
};
Console.WriteLine(typeName); // 出力: これは整数
var パターンは型チェックだけに使うことは少ないけど、他のパターンや switch の中で値を取り出すのに便利だよ。
5. is vs as vs Pattern Matching: どれをいつ使う?
どのツールを使うかは、やりたいことやC#のバージョンによるよ。
is オペレーター (シンプルな使い方):
使いどころ: オブジェクトが特定の型かどうか だけをチェックしたい時。キャストや特有のメンバーアクセスがすぐ必要じゃない場合。
if (myObject is SomeType)
as オペレーター:
使いどころ: 参照型 (またはnullable値型) をキャストしたい けど、null チェックで失敗を処理したい時。例外を投げたくない場合に便利。
MyDerivedClass derived = myBaseObject as MyDerivedClass;
if (derived != null) { /* ... */ }
Pattern Matching (is Type variable):
今どきのおすすめ: 型チェックと安全なキャストを1行でできて、読みやすい。どんな型にも使える。
使いどころ: 型チェックして、すぐにその型特有のメンバーを使いたい 時。
if (myObject is MyDerivedClass derived) { derived.SpecificMethod(); }
Pattern Matching (switch expressions / statements):
今どきのおすすめ: 型やプロパティによって違う処理や値を返したい 時に最適。長い if-else if よりずっとスッキリ書ける。
使いどころ: オブジェクトの特徴に応じてポリモーフィックな振る舞いや複雑な分岐ロジックを実装したい時。
string GetShapeInfo(Shape s) => s switch
{
Circle c => $"円 R={c.Radius}",
Rectangle r => $"長方形 W={r.Width} H={r.Height}",
_ => "不明"
};
GO TO FULL VERSION