ここではswitch文で利用できる型パターンマッチングを学習します。
正直、正式な呼び名を知りません。単にパターンマッチングと呼ぶ気もします。
ここではC#のswitch文について理解しましょう。これはプログラムの基本構造で説明した選択にあたる処理です。なお、プログラムの基本構造が分からない人は先に次の記事を読んでください。 switch statement (ステートメント) if文と同じく条件分岐の処理になります。基本的にはif文と同じなのですが、switch文のほうが高度な記述ができます。 パターン1: switch 定数値 最もシンプルな記述はこれです。 評価値に記述できるの...
型パターンマッチング
型パターンマッチングとは、switch文で型を基準に分岐する方法です。
まず、次のようなクラス設計があったとします。
internal abstract class Role
{
}
internal class Attacker : Role
{
public void Attack()
{
Console.WriteLine($"攻撃");
}
}
internal class Healer : Role
{
public void Heel()
{
Console.WriteLine($"回復");
}
}
internal class Tank : Role
{
public void Defense()
{
Console.WriteLine($"守る");
}
}
よくあるゲームの役割的なやつ。
補足本来は抽象メソッドにしたほうが正しいのですが、それは本題とズレるので無視します。
では、これらのクラスを上位クラスのRole型で管理する場合、各クラスのメソッドには、どのようにアクセスするのでしょうか?
最も簡単なのはダウンキャストします。また、普通は明示的キャストは使わないのでis演算子とかを使います。
namespace Sample
{
internal abstract class Role
{
}
internal class Attacker : Role
{
public void Attack()
{
Console.WriteLine($"攻撃");
}
}
internal class Healer : Role
{
public void Heel()
{
Console.WriteLine($"回復");
}
}
internal class Tank : Role
{
public void Defense()
{
Console.WriteLine($"守る");
}
}
internal class Program
{
static void Main(string[] args)
{
var roles = new Role[]
{
new Attacker(),
new Healer(),
new Tank(),
};
foreach (var role in roles)
{
if (role is Attacker attacker)
{
attacker.Attack();
}
else if (role is Healer healer)
{
healer.Heel();
}
else if (role is Tank tank)
{
tank.Defense();
}
}
}
}
}
型パターンマッチングってのは、このパターンをswitch文で記述する方法です。
先程のパターンなら、こんな感じのswitch文に書き換えができます。
namespace Sample
{
internal abstract class Role
{
}
internal class Attacker : Role
{
public void Attack()
{
Console.WriteLine($"攻撃");
}
}
internal class Healer : Role
{
public void Heel()
{
Console.WriteLine($"回復");
}
}
internal class Tank : Role
{
public void Defense()
{
Console.WriteLine($"守る");
}
}
internal class Program
{
static void Main(string[] args)
{
var roles = new Role[]
{
new Attacker(),
new Healer(),
new Tank(),
};
foreach (var role in roles)
{
// 型パターンマッチング
switch (role)
{
case Attacker attacker:
attacker.Attack();
break;
case Healer healer:
healer.Heel();
break;
case Tank tank:
tank.Defense();
break;
}
}
}
}
}
部分的に抜き出すならここ。caseに型と変数を記述します。
ちなみに変数が不要な場合は型のみの記述でも動作します。
switch (role)
{
case Attacker attacker:
attacker.Attack();
break;
case Healer healer:
healer.Heel();
break;
case Tank tank:
tank.Defense();
break;
}
また、switch文で記述する場合、caseで定義する変数名は同名でも問題ありません。
対して、これと同じことをis演算子でやるとビルドエラーで動かなくなります。
namespace Sample
{
internal abstract class Role
{
}
internal class Attacker : Role
{
public void Attack()
{
Console.WriteLine($"攻撃");
}
}
internal class Healer : Role
{
public void Heel()
{
Console.WriteLine($"回復");
}
}
internal class Tank : Role
{
public void Defense()
{
Console.WriteLine($"守る");
}
}
internal class Program
{
static void Main(string[] args)
{
var roles = new Role[]
{
new Attacker(),
new Healer(),
new Tank(),
};
foreach (var role in roles)
{
// 型パターンマッチング
switch (role)
{
case Attacker _role:
_role.Attack();
break;
case Healer _role:
_role.Heel();
break;
case Tank _role:
_role.Defense();
break;
}
}
}
}
}
例では継承関係の存在するクラスを利用しましたが、この仕組みは継承関係が無くても利用できます。
普通はやりませんが、object型でまとめた非継承関係のクラスでも、パターンマッチングは動作します。
namespace Sample
{
internal class Attacker
{
public void Attack()
{
Console.WriteLine($"攻撃");
}
}
internal class Healer
{
public void Heel()
{
Console.WriteLine($"回復");
}
}
internal class Tank
{
public void Defense()
{
Console.WriteLine($"守る");
}
}
internal class Program
{
static void Main(string[] args)
{
var roles = new object[]
{
new Attacker(),
new Healer(),
new Tank(),
};
foreach (var role in roles)
{
// 型パターンマッチング
switch (role)
{
case Attacker attacker:
attacker.Attack();
break;
case Healer healer:
healer.Heel();
break;
case Tank tank:
tank.Defense();
break;
}
}
}
}
}
object型に何でも放り込むのは、ろくなパターンじゃ無いので非推奨。
あとがき
言語バージョンが上がるたびにswitch文は進化してます。
特にパターンマッチングなんかは種類が増えすぎて覚えられない。
◆ C#に関する学習コンテンツ
この記事は参考になりましたか?
コメント