C#で継承をしてみよう!基礎知識から抽象クラスまでの総まとめ

オブジェクト指向の 「継承」を理解しよう

こんにちは、元エンジニアでC#ライターの遠藤です!


継承ってなに? やり方も知りたい。
オーバーライドって何?
インタフェースの継承ってどういう事?

オブジェクト指向言語において欠かす事のできない継承ですが、なかなか理解が難しい内容でもありますよね。

今回の記事は、こんな方へ向けて解説していきます。

  • 継承とは何か知りたい
  • 継承のやり方が知りたい
  • オーバーライドについて知りたい
  • インタフェースや抽象クラスの継承について知りたい

目次

継承とは

継承とは、あるクラスの機能を受け継いで新しいクラスを作ることを言います。

具体的な例は次の章で紹介しますが、例えばPersonクラスを継承してEmployeeクラスを作ったり、Clockクラスを継承してWatchクラスを作ったりするといったイメージです。

継承をしたさい元になるクラスを基底クラス(スーパークラス)、継承したクラスを派生クラス(サブクラス)と言います。

継承の基本

それでは実際のコード例を見てみましょう。ここでは、基底クラスはPersonクラス、派生クラスはEmployeeクラスになります。

using System;

namespace SampleApplication1
{
    class Person //基底クラス
    {
        string name;
        int age;
        public Person(string name, int age) //基底クラスのコンストラクタ
        {
            this.name = name;
            this.age = age;
            Console.WriteLine("基底クラス");
            Console.WriteLine("名前: " + name + ", 年齢: " + age);
        }
    }
    class Employee : Person // 派生クラス
    {
        int id;
        string department;
        public Employee(string name, int age, int id, string department) : base(name, age) //派生クラスのコンストラクタ
        {
            this.id = id;
            this.department = department;
            Console.WriteLine("派生クラス");
            Console.WriteLine("社員番号: " + id + "部署: " + department);
        }
        public static void Main()
        {
            Employee empl_1 = new Employee("ichiro", 25, 123456, "system"); //インスタンスを生成
        }
    }
}

実行結果:

基底クラス
名前: ichiro, 年齢: 25
派生クラス
社員番号: 123456部署: system

継承を実装するには、「class 派生クラス名 : 基底クラス名」と記述します。サンプルでは17行目です。

また、基底クラスのコンストラクタに値を渡したいときは、「派生クラスのコンストラクタ名 : base(引数)」と記述します。こちらはサンプルの21行目が該当します。

実行結果を見てみると、派生クラスのインスタンスを生成したさいに基底クラスで実装された処理が実行されている事が分かります。基底クラスの処理 -> 派生クラスの処理という順番になっている点も覚えておきましょう。

もしクラスやコンストラクタについて分からない方は以下記事を参照していただければと思います。

こちらの記事はクラスの基礎知識がまとめられています。

こちらの記事はコンストラクタの基礎知識と、継承した際にどういう順番でコンストラクタが実行されるのかが分かります。

オーバーライド

派生クラスでは、基底クラスのメソッドを上書きして独自の機能を持たせる事が出来ます。これをオーバーライドと言います。

実際の例を確認しましょう。

using System;

namespace SampleApplication1
{
    class SuperClass
    {
        private DateTime now;

        public virtual String SampleMethod()
        {
            return "基底クラスのメソッド";
        }
    }
    class SubClass : SuperClass
    {
        public override String SampleMethod()
        {            
            return base.SampleMethod() + "をオーバーライドしました。";
        }
        public static void Main()
        {
            SubClass test = new SubClass();
            Console.WriteLine(test.SampleMethod());
        }
    }
}

実行結果:

基底クラスのメソッドをオーバーライドしました。

書き方のポイントは、以下になります。

  • 9行目: 基底クラスでオーバーライドされるメソッドにvirtualを記述する(仮想メソッドと言います)
  • 15行目: 派生クラスで基底クラスのメソッドと同じ名前のメソッドを宣言する
  • 15行目: オーバーライドするメソッドにはoverrideを記述する
  • 15行目: 戻り値は同じにする
  • 16行目: 派生クラスのメソッドに上書きする処理を書く
  • 16行目: もし元のメソッドの処理が必要な場合はbaseを使う

インタフェース

インタフェースは、継承される事を前提として継承したクラスで処理の実装を強制させるものです。

言葉では全然分かりづらいですね……。実際のコードをご確認ください。

using System;

namespace SampleApplication1
{
    interface IClock //インタフェースの名前はIから始まる
    {
        void getTime();
    }
    class Watch : IClock //継承
    {
        public void getTime() //インタフェースで宣言したメソッドを実装する。しないとエラー。
        {
            Console.WriteLine("ただいまの時刻は0:00です。");
        }
        public static void Main()
        {
            Watch smartWatch = new Watch();
            smartWatch.getTime();
        }
    }
}

実行結果:

ただいまの時刻は0:00です。

このように、インタフェースで宣言したメソッドを派生クラスで実装しなくてはならなくなります。こうすることで、必須となるメソッドを設定する事ができます。

インタフェースの特徴としては、実装は行わず必要なメソッドの定義だけを行うという点です。7行目のように、メソッド名の宣言だけします。

また、インタフェースから直接インスタンスは生成できません

インタフェース実装のポイントは以下になります。

  • 5行目: クラス同様、「interface インタフェース名」というように宣言する
  • 7行目: インタフェース内で、定義するメソッド名だけを宣言する
  • 11行目: 派生クラスでは、継承したインタフェースで定義されたメソッドの処理を実装する必要がある
  • overrideの記述は不要

抽象クラス

抽象クラスはインタフェースと似ています。派生クラスで実装が必須になる点、インスタンスは生成できない点がインタフェースと同じになります。

差分としては、抽象クラスでは抽象メソッド以外にメソッドを実装することができる点です。実際の例を確認しましょう。

using System;

namespace SampleApplication1
{
    abstract class Clock //抽象クラスの宣言
    {
        public void alerm() //抽象メソッド以外を宣言、処理の記述ができる
        {
            Console.WriteLine("15:00にアラームを鳴らします");
        }
        abstract public void getTime(); //抽象メソッドの宣言
        //抽象メソッドは処理を記述できません。
    }
    class Watch : Clock //継承
    {
        public override void getTime() //オーバーライドする必要がある
        {
            Console.WriteLine("ただいまの時刻は0:00です。");
        }
        public static void Main()
        {
            Watch smartWatch = new Watch();
            smartWatch.getTime();
            smartWatch.alerm();
        }
    }
}

実行結果:

ただいまの時刻は0:00です。
15:00にアラームを鳴らします

抽象クラスの実装ポイントは以下になります。

  • 5行目: 抽象クラス宣言時に「abstract class クラス名」と記述する
  • 11行目: 派生クラスで実装させたいメソッドを「abstract public/protected 型 メソッド名()」と記述して宣言する
  • 16行目: 派生クラスでは、継承したインタフェースで定義されたメソッドの処理を実装する必要がある
  • 16行目: メソッドを実装する際は、overrideを記述する

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

ここで疑問になるのが、インタフェースと抽象クラスの違いです。上記の説明だけだと、メソッドの実装もできる抽象クラスを常に使えば良いのではと考えてしまいますよね。

インタフェースにも利点があります。それは、派生クラスで複数のインタフェースを継承できるという点です。

継承は基本的に1対1でしか利用できませんが、インタフェースは例外となります。実際の例を見てみましょう。

using System;

namespace SampleApplication1
{
    interface IClock //インタフェースの名前はIから始まる
    {
        tTime();
    }
    interface IDevice
    {
        void ElectronicControl();
    }
    
    class Watch : IClock, IDevice //2重で継承している
    {
        public void getTime() //インタフェースで宣言したメソッドを実装する。しないとエラー。
        {
            Console.WriteLine("ただいまの時刻は0:00です。");
        }
        
        public void ElectronicControl()
        {
            Console.WriteLine("電子決済とかします");
        }
        public static void Main()
        {
            Watch smartWatch = new Watch();
            smartWatch.getTime();
            smartWatch.ElectronicControl();
        }
    }
}

実行結果:

ただいまの時刻は0:00です。
電子決済とかします

このように、インタフェースに関してはカンマで区切れば複数継承する事が可能になります。

上記のように実装すれば、ClockとDeviceのインタフェースを利用して、SmartWatchというクラスが定義できるようになります。

まとめ

今回の記事では、以下の点について解説致しました。

  • 継承の基礎知識
  • オーバーライド
  • インタフェース
  • 抽象クラス

冒頭でも述べましたが、継承はオブジェクト指向で欠かすことのできない要素であるにもかかわらずとても難しいです。

この記事が少しでも理解の助けになれれば幸いです。

この記事を書いた人

1991年生まれ。双子座。
理系大学で認証システムを学んだ後、アプリ開発者となる。
新しく学ぶ人に寄り添った記事を心がけて執筆します。
芸術が好き。いつか猫と暮らすのが夢。

目次