注目キーワード
  1. プログラミング
  2. C#

【C#】クラスを作成する方法(上級編①:抽象)

C#

はじめに

こうちゃ
こんにちは、こうちゃです。
今回は、C#のクラス作成における抽象メソッドについて説明していきます。
今回の内容は、下記記事の続きになります。
クラスは何記事かにわけて解説しています。
関連記事

はじめに こうちゃ こんにちは、こうちゃです。 今回は、C#のクラス作成における継承について説明していきます。 今回の内容は、下記記事の続きになります。 クラスは何記事かにわけて解説予定です。 [sitecard[…]

  • 抽象メソッド / クラス とは何か
  • 抽象クラスとインターフェースの違い
  • クラスを作成したい方
  • オブジェクト指向に興味のある方
こうちゃ
それでは、やっていきましょう!

抽象 (abstract) とは

抽象クラス / メソッド

抽象とは 処理の中身は定義せず、子クラスに実装を強制する仕組み になります。

このようなメソッドを 抽象メソッド といい、抽象メソッドが定義されているクラスを 抽象クラス と言います。
基本的なルールのみを定義し、実際の挙動はそれぞれの子クラスに任せたい時に使用します。

抽象クラスの作り方

言葉だと分かりにくいので、実際にコードを見てみましょう。

namespace Characters
{
    abstract class Enemy
    {
        // メンバ変数
        protected string Name { get; private set; }  // 固有名

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="name"></param>
        public Enemy(string uniqueName)
        {
            // 固有名は引数で渡してもらう
            Name = uniqueName;

            Console.WriteLine($"{Name} が生成されました。");
        }

        /// <summary>
        /// 攻撃メソッド(抽象メソッド)
        /// </summary>
        public abstract void Attack();
    }
}

public abstract void Attack();」が抽象メソッドになっています。
あくまで定義がされているだけで、中身は実装していません。

またこのEnemyクラスは抽象メソッド「Attack」があるため抽象クラスになります。
抽象クラスの場合は「abstract class Enemy」というようにクラスを定義します。
1つ重要な点として、抽象クラスはインスタンス化できない という特徴があるので覚えておきましょう。

抽象クラス作成時のポイント

  • 抽象クラスは「abstract class クラス名」と定義し、インスタンス化はできない
  • 抽象メソッドは「アクセス修飾子 abstract 戻り値 メソッド名;」と定義し、実装は書かない

抽象クラスの使い方

続いて、先ほど作成した抽象クラス「Enemy」を使用してみましょう。

namespace Characters
{
    class Mob : Enemy
    {
        /// <summary>
        /// コンストラクタ(Enemyクラスのコンストラクタを呼び出す)
        /// </summary>
        /// <param name="uniqueName">固有名</param>
        public Mob(string uniqueName) : base(uniqueName)
        {
        }

        /// <summary>
        /// EnemyクラスのAttackメソッドをオーバーライド
        /// </summary>
        public override void Attack()
        {
            Console.WriteLine($"{Name}は通常攻撃を繰り出した!");
        }
    }
}
namespace Characters
{
    class Boss : Enemy
    {
        /// <summary>
        /// コンストラクタ(Enemyクラスのコンストラクタを呼び出す)
        /// </summary>
        /// <param name="uniqueName">固有名</param>
        public Boss(string uniqueName) : base(uniqueName)
        {
        }

        /// <summary>
        /// EnemyクラスのAttackメソッドをオーバーライド
        /// </summary>
        public override void Attack()
        {
            Console.WriteLine($"{Name}は強力な通常攻撃を繰り出した!!");
        }
    }
}

抽象クラス「Enemy」を継承した「Mob」クラスと「Boss」クラスを作成しました。

コンストラクタは前回の記事でも取り上げましたが、親クラス「Enemy」のコンストラクタを呼び出しています。

そして今回注目は「public override void Attack()」です。
ここで、今までは見なかった override という用語が登場しました。

「override」は、親クラスで定義されているメソッドを子クラスで再度定義する時に必要な用語 と考えると分かりやすいでしょう。
今回は、親クラス「Enemy」で抽象メソッドとして「Attack」というメソッドが定義済みです。(実装は空ですが)
そのため、「Attack」メソッドの実装を書く際には「override」をつける必要があります。

さて、注目の「Attack」メソッドですが、「Mob」と「Boss」どちらのクラスでも実装されていますが、その中身は異なっています。
抽象メソッドを使用すると、継承した先で異なる処理を実装することが可能 になることがわかります。

また、試しに「Attack」メソッドを実装せずにビルドしてみましょう。

error CS0534: 'Boss' は継承抽象メンバー 'Enemy.Attack()' を実装しません

上記のようなエラーが出ます。
このように、抽象メソッドは必ず子クラスで実装しないといけません。

抽象クラス使用時のポイント

  • 「override」を使用して、抽象メソッドを必ず実装する必要がある
    (実装しないとコンパイルエラー)

抽象クラスとインターフェースの違い

抽象クラスは「必要となる機能をルールとして定め、実際の内容は子クラスに任せる」ものだということがわかりました。

しかしこのような性質、以前もどこかで見た気がしませんか?
そうです、インターフェースも同じような性質を持っています。

関連記事

はじめに こうちゃ こんにちは、こうちゃです。 今回は、C#のクラス作成においてインターフェースの使用方法を説明していきます。 今回の内容は、下記記事の続きになります。 クラスは何記事かにわけて解説予定です。 [[…]

それでは、抽象クラスとインターフェースの違いはなんでしょうか?

以前作成した「IEnemy」インターフェースと、今回作成した「Enemy」抽象クラスを比較してみましょう。

namespace Characters
{
    interface IEnemy
    {
        void Attack();
    }
}
namespace Characters
{
    abstract class Enemy
    {
        // メンバ変数
        protected string Name { get; private set; }  // 固有名

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="name"></param>
        public Enemy(string uniqueName)
        {
            // 固有名は引数で渡してもらう
            Name = uniqueName;

            Console.WriteLine($"{Name} が生成されました。");
        }

        /// <summary>
        /// 攻撃メソッド(抽象メソッド)
        /// </summary>
        public abstract void Attack();

        public void Move()
        {
            Console.WriteLine($"{Name}は移動した!!");
        }
    }
}

インターフェース:

  • 「Attack」という機能を持っていることを約束している

抽象クラス

  • 「Attack」という機能を持っていることを約束している
  • メンバ変数 (プロパティ) 「Name」を持っている
  • コンストラクタを定義できる

上記のコードを見るだけでも、これだけの違いがあります。
今回は例として取り上げていませんが、抽象クラスは抽象メソッドではない普通のメソッドも実装できます
こう見ると、結構違いがありますよね。

抽象クラスの方ができることが多いように見えますが、デメリットもあります。
なので、抽象クラスとインターフェースでできること / できないことを表にまとめました。

抽象クラスとインターフェースのできること / できないこと

項目 抽象クラス インターフェース
メンバ変数を持てる ⭕️
コンストラクタを持てる ⭕️
実装済みメソッドを持てる ⭕️ ❌ (C# 8.0以降はデフォルト実装が可能ではある)
多重継承 ⭕️

まとめ

抽象クラスとインターフェースは以下のような使い方が良いでしょう。

抽象クラス   :「共通メソッドの作成 + 一部の機能を子クラスに任せたい」場合
インターフェース:「何種類もの異なる機能の約束」をしたい場合

RPGを例にとると↓のような感じ

  • 「敵キャラ」に共通する部分の処理 + 絶対に持っている機能 (攻撃など):抽象クラス
  • 「飛行」という機能を持っている:インターフェース
  • 「一撃必殺」という特殊機能を持っている:インターフェース

さいごに

今回は、「抽象」について説明しました。

抽象クラスは、インターフェース + クラス である程度再現可能であることは否定できません。
それでも「抽象クラス」が存在し続けているのは、「インターフェースとクラスの中間」という立ち位置にメリットがあるからだと思います。
正直、抽象クラスの扱いやすさを伝えるのは難しく、理解するには実際に使用してみる必要があると思っています。
なので、この記事を読んで抽象クラスに少しでも興味を持った方はぜひ使ってみてください。
使えば使うほど、扱いやすさがわかってくると思います。

「抽象」を取り扱うと「仮想」の説明も必要になります。
この記事で取り扱うと肥大化しそうだったので別記事で紹介したいと思います。
今回はここまで♪

こうちゃ
それではみなさま、お疲れ様でした!
楽しいプログラミングライフを!
最新情報をチェックしよう!