【C#】クラスを作成する方法(上級編②:仮想)

C#

はじめに

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

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

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

仮想 (virtual) とは

仮想とは 親クラスで実装を持ちながら、子クラスで上書きをできるようにする仕組み になります。
抽象は 子クラスに実装を強制する 仕組みでした。
仮想は 実装は元々存在するが、必要なら子クラスで書き換え可能 という仕組みです。

仮想メソッドの作り方

仮想メソッドを実際に作っていきましょう。
前回の「Enemy」クラスを少し変更しました。

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 virtual void Attack()
        {
            Console.WriteLine($"{Name} は通常攻撃を繰り出した!");
        }
    }
}

public virtual void Attack()」が仮想メソッドになっています。
前回の抽象メソッドとは違い、仮想メソッドでは実装可能なことがわかります。

仮想メソッド (親クラス) のポイント

  • 仮想メソッドは「アクセス修飾子 virtual 戻り値 メソッド名;」と定義する
  • 親クラスに実装がある

仮想メソッドの使い方

続いて、先ほどの「Enemy」クラスを継承し、子クラスで上書きしてみましょう。
「Mob」と「Boss」の2クラスを作成しました。

namespace Characters
{
    class Mob : Enemy
    {
        /// <summary>
        /// コンストラクタ(Enemyクラスのコンストラクタを呼び出す)
        /// </summary>
        /// <param name="uniqueName">固有名</param>
        public Mob(string uniqueName) : base(uniqueName)
        {
        }
    }
}
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()
        {
            base.Attack();  // EnemyクラスのAttackメソッドも呼び出す
            Console.WriteLine($"{Name} はもう一度通常攻撃を繰り出した!!");
        }
    }
}

「Mob」クラスでは「Attack」メソッドの実装がありません。
この場合、通常の継承と同様に、親クラスで実装されてる「Attack」メソッドが呼び出されます。

一方で「Boss」クラスでは「Attack」メソッドが実装されています。
public override void Attack()」というように、抽象メソッドと同様 override をつけることで上書き可能になります。

また「Boss」クラスの「Attack」メソッドに「base.Attack();」という記述があります。
これは、親クラスの「Attack」メソッドを呼び出すことを意味します。
このように、子クラスでオーバーライドしたメソッドで親クラスの仮想メソッドを呼び出すことも可能です。

仮想メソッド (子クラス) のポイント

  • 「override」で上書き可能
  • 「override」しない場合は親クラスのメソッドが呼び出される
  • 「override」した場合も「base.メソッド名」で親クラスのメソッドを呼び出し可能

仮想と抽象の違い

前回の記事で「抽象」今回の記事で「仮想」を取り上げました。
この2つは概念的に似ている部分があります。
何が違うのでしょうか?

一番大きな違いは 自身が実装を持てるか否か でしょう。
抽象メソッドは自身で実装できませんでしたが、仮想メソッドは実装可能です。

次に 子クラスでoverrideは必須か任意か という違いがあります。
抽象メソッドの場合は子クラスでoverride必須ですが、仮想メソッドでは任意ですね。

あとは インスタンス化が可能か否か、通常クラスで使用可能か否か になります。
抽象メソッドは抽象クラスでしか使用できず、抽象クラスはインスタンス化ができませんでした。
一方、仮想メソッドは通常クラスに定義可能で、インスタンス化もできます。

違いをまとめると以下のようになります。

項目 抽象 仮想
親クラス実装 持たない 持つ
子クラスoverride 必須 任意
クラス 抽象クラスのみ 通常クラスでも可
インスタンス化 できない できる

こう見ると、結構違いますね。

仮想メソッドのメリット

さて、抽象メソッドとの違いもわかったところで、仮想メソッドのメリットを考えていきましょう。

仮想メソッドのメリット
それは 非常に柔軟に設計ができる という点です。

親クラスでデフォルト動作を決定できるため、基本的には子クラスでの実装が必要ありません。
それにより、同じようなコードが何箇所にもあるということがなくなります。
(抽象メソッドの場合、同じ処理でもすべての子クラスで実装が必要)

それでいて、必要な場合は「override」で処理を上書きしてあげればよく、さらには親クラスのデフォルト動作も実行できます。

この 共通動作を持ちながら、必要なクラスだけ動作を変更できる というのは、クラスを使用する上で非常に助かります。

設計をシンプルに保ちながら拡張できる これが仮想メソッドの大きなメリットと言えるでしょう。

さいごに

今回は「仮想」について解説しました。

これまで「インターフェース」「継承」「抽象」「仮想」とクラスの重要な部分を続けて取り上げてきましたが、わかりやすかったでしょうか?

このあたりは、概念が理解できていることと、実際にうまく使用できることはイコールじゃないと思っています。
なので、どんどん使ってみてください!

次は、今まであまり触れられていない「多態性」についての記事か、今までの内容をまとめた形にする記事か、どちらかにしようかと思っています。
今回はここまで♪

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