switch文の型パターンマッチングを理解しよう!

[C#] switch文の型パターンマッチングを理解しよう!

※ 当サイトは広告を含みます。

ここでは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#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。