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

【C#】クラスを作成する方法(中級編②:継承)

C#

はじめに

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

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

  • 継承とはなにか
  • 継承のやり方
  • クラスを作成したい方
  • オブジェクト指向に興味のある方
こうちゃ
それでは、やっていきましょう!

継承とは

クラス/インターフェースには、継承 というものがあります。
継承とは、
任意のクラスやインターフェースの機能を引き継いで、新しいクラス/インターフェースを作る仕組み
と表現することができます。
実は、前回の記事でも継承が使われていました。
class MobWeak : IEnemy」というコードがあったのを覚えているでしょうか?
この「: IEnemy」が継承を表していて、
「MobWeakクラスはIEnemyインターフェースの機能を引き継いでいますよ」
ということを意味しています。
実際に、IEnemyに定義されている「Attack」メソッドをMobWeakクラスに実装する必要がありましたね。
(MobStrongクラスもIEnemyを継承していて「Attack」メソッドを実装していました)
このように、共通の機能を持っているが、異なるクラスを作成したい場合 に継承はとても役立ちます。

継承のやり方

さて、継承のやり方に移っていきましょう。
継承には
  • インターフェースの継承
  • クラスの継承

があります。

インターフェースの継承

前回の記事でも取り上げられていたので、インターフェースの継承からやっていきましょう。
インターフェースの継承は

  • インターフェースがインターフェースを継承する
  • クラスがインターフェースを継承する

のどちらも行うことができます。

インターフェースがインターフェースを継承する

まずは「インターフェースがインターフェースを継承する」について見ていきましょう。

前回同様、IEnemyインターフェースが以下のように定義されています。

namespace Characters
{
    interface IEnemy
    {
        void Attack();
    }
}

このIEnemyインターフェースを継承したIBossインターフェースを作成しましょう。

namespace Characters
{
    interface IBoss : IEnemy
    {
        void SpecialAttack();
    }
}

これで、IBossインターフェースは「Attack」メソッドと「SpecialAttack」メソッドの両方を兼ね備えている ということになります。

「インターフェースがインターフェースを継承する」のポイント

  • 「interface 新規インターフェース名: 継承インターフェース名」で継承可能
  • 新規インターフェースは、継承インターフェースのメソッドを引き継ぐ
    (IBossインターフェースが「Attack」メソッドを引き継いでいる)
  • 新規インターフェースでメソッドを追加可能

クラスがインターフェースを継承する

続いて、IBossインターフェースが本当に両方のメソッドを兼ね備えているのかの確認も兼ねて、「クラスがインターフェースを継承する」を見ていきたいと思います。

IBossインターフェースを継承したBossクラスを作成します。

namespace Characters
{
    class Boss : IBoss
    {
        // メンバ変数
        private string name;     // 固有名

        /// <summary>
        /// コンストラクタ1つ目(インスタンス生成時に呼ばれる)
        /// </summary>
        /// <param name="uniqueName">固有名</param>
        public Boss(string uniqueName)
        {
            // 固有名は引数で渡してもらう
            name = uniqueName;

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

        /// <summary>
        /// IEnemyインターフェースのAttackメソッド実装
        /// </summary>
        public void Attack()
        {
            Console.WriteLine($"{name}は通常攻撃を繰り出した!!");
        }

        /// <summary>
        /// IBossインターフェースのSpecialAttackメソッド実装
        /// </summary>
        public void SpecialAttack()
        {
            Console.WriteLine($"{name}は必殺技を繰り出した!!");
        }
    }
}

BossクラスはIBossインターフェースを継承しているため、「Attack」「SpecialAttack」両方のメソッドを実装しないとエラーになります。
「SpecialAttack」メソッドを実装せずにビルドすると、↓のようなエラーが出ますね。

error CS0535: 'Boss' はインターフェイス メンバー 'IBoss.SpecialAttack()' を実装しません

このように、インターフェースを継承する場合、そのインターフェースが定義してあるメソッドは必ず実装しないといけません

また、インターフェース継承には、
継承するインターフェースの数に制限はない
という特徴があります。
次の「クラスの継承」で関わってくるので、今は「ふーん、そうなんだ〜」くらいに思っておいてください。

「クラスがインターフェースを継承する」のポイント

  • 「class クラス名 : 継承インターフェース名」で継承可能
  • 継承インターフェースが兼ね備えているメソッドは必ず実装する
  • インターフェースはいくつでも継承可能

クラスの継承

クラスの継承では、既に実装されているクラスの機能を引き継いで、新たなクラスを作成します。
ここで重要な用語です。
このクラスの継承において、基底クラス派生クラス という概念が存在します。

  • 基底クラス:別名「親クラス」。継承の基となるクラスを表す。
  • 派生クラス:別名「子クラス」。親クラスを継承して新規に作成されるクラスを表す。

この2つはとても重要な用語なので覚えておきましょう。

では、簡単な親クラスを作成していきましょう。

namespace Characters
{
    class Mob : IEnemy
    {
        // メンバ変数
        private string name;     // 固有名

        /// <summary>
        /// コンストラクタ1つ目(インスタンス生成時に呼ばれる)
        /// </summary>
        /// <param name="uniqueName">固有名</param>
        public Mob(string uniqueName)
        {
            Console.WriteLine($"Mobコンストラクタが呼び出されました。");
            // 固有名は引数で渡してもらう
            name = uniqueName;

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

        /// <summary>
        /// IEnemyインターフェースのAttackメソッド実装
        /// </summary>
        public void Attack()
        {
            Console.WriteLine($"{name}は1回攻撃した!");
        }
    }
}

親クラスであるMobクラスを作成しました。

では続いて、このMobクラスを継承したクラス「MobWeak」を見ていきましょう。

namespace Characters
{
    class MobWeak : Mob
    {
        /// <summary>
        /// コンストラクタ1つ目(インスタンス生成時に呼ばれる)
        /// </summary>
        /// <param name="uniqueName">固有名</param>
        public MobWeak(string uniqueName) : base(uniqueName)
        {
            Console.WriteLine($"MobWeakコンストラクタが呼び出されました。");
        }
    }
}

インターフェース継承と同様で、「: 継承クラス名」でクラスの継承は可能です。

このMobWeakクラス、コンストラクタに今まで見たことない記述があると思います。
public MobWeak(string uniqueName) : base(uniqueName)」の部分ですね。
これは、
MobWeakのコンストラクタが呼ばれると、「base」つまり基底クラス (親クラス) のコンストラクタを呼び出します
ということを表しています。

また、Mobクラスでは「Attack」メソッドが実装されていたのに、MobWeakクラスでは「Attack」メソッドが実装されていません。

ここがクラス継承の特殊なところで、
親クラスで実装されているメソッドを子クラスで実装しない場合、自動的に親クラスに実装されているメソッドが呼び出される
という性質があります。

つまり、MobWeakクラスのインスタンスで「Attack」メソッドが呼び出されたら、自動的にMobクラスの「Attack」メソッドが呼び出されます。

↑これ、本当なんですかね?
確かめるために、以下のコードを書いて実行して見ましょう。

using Characters;

namespace Main
{
    class Program
    {
        static void Main(string[] args)
        {
            Mob mob1 = new MobWeak("弱モブ1");
            mob1.Attack();
            MobWeak mob2 = new MobWeak("弱モブ2");
            mob2.Attack();
        }
    }
}

Mobクラスインスタンスである「mob1」(中身はMobWeakクラス) 、MobWeakクラスインスタンスである「mob2」を作成して見ていきましょう。

実行結果↓

Mobコンストラクタが呼び出されました。
弱モブ1 が生成されました。
MobWeakコンストラクタが呼び出されました。
弱モブ1は1回攻撃した!
Mobコンストラクタが呼び出されました。
弱モブ2 が生成されました。
MobWeakコンストラクタが呼び出されました。
弱モブ2は1回攻撃した!

はい、このように、「mob1」でも、「mob2」でも同様の結果が得られましたね。
また、MobWeakクラスには「Attack」メソッドは実装していないのに、「mob2.Attack()」ができています。

この結果からわかることは、

  • インスタンスを宣言した型に関わらず、newしたクラスでインスタンスは作成される。
    (今回なら、「mob1」はMobクラスの型だが、実態はMobWeak)
  • 子クラスのコンストラクタを呼び出すと
    親クラスコンストラクタ→子クラスコンストラクタ
    の順番で呼び出される。
  • 親クラスに実装されていて子クラスに実装されていないメソッドが子クラスインスタンスから呼び出された場合、親クラスに実装されているメソッドが呼び出される

の3点になります。

さてここで、クラス継承について大事なことを1つ言います。

クラス継承では
複数のクラスを継承することはできない
という特性があります。(あくまでC#の話で、プログラミング言語によっては複数クラス継承が可能だったと思います)

インターフェース継承はいくつも可能でしたが、クラスは単一継承しか認められていませんので、覚えておいてください。

「クラスの継承」のポイント

  • 「class 子クラス名 : 親クラス名」で継承可能
  • 子クラスは、親クラスが持っている機能を受け継ぐことができる
    (親クラスで実装されているメソッドは子クラスでも使用できる)
  • ゆえに、共通処理を親クラスにまとめて、複数の子クラスで再利用ができる
  • 単一継承のみ(複数クラスの継承はできない)

さいごに

今回は、クラス/インターフェースの継承について見てきました。
内容が多くなってしまいましたね…。

継承はルールがいろいろあるので、理解するのがわりと大変だと思います。
ですが、今回の内容はまだ継承の中では序の口です。
これからさらに「抽象」や「仮想」という概念が出てきます。

これらの概念はオブジェクト指向を支える重要部分だと私は思っています。
そのため、また内容が多くなってしまうでしょう…。
なので、また次の記事にまわしたいと思います。
今回はここまで♪

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