【C#】イメージしづらいオーバーライドを3ステップで完全理解!

こんにちは!文系出身ながらも自力で勉強・実務をこなし、8年目に突入した現役プログラマの佐藤です。

早速ですが、みなさんはオーバーライドについて理解できていますか?
C#を勉強している方なら、必ず勉強することになるオーバーライドですが


オーバーライドの仕組みがうまくイメージできない
どんな時に使えばよいのか分からない
覚える必要あるの?

 
オーバーライドはイメージが掴みにくく難しい印象になりがちですよね。その印象のせいか、オーバーライドの使用・勉強を避けてしまう方もいるかと思います。

ですが、オーバーライドはプログラミング学習の中でも非常に大切な要素の一つなんです!理解出来た人と、出来ていない人ではこの先の学習に必ず影響が出ます。

そんなオーバーライドという要素を仕組みから理解し、使いこなせるように、この記事ではオブジェクト指向の基礎から復習していきます。

そして、メリット・なぜ大切な要素なのかという部分もしっかり解説していきます。

もちろん、後半ではサンプルコードを使って具体的な実装方法も紹介します!それでは早速始めていきましょう!

目次

オーバーライドを理解するための3大要素

この章では、オーバーライドを理解するために重要な3つの要素について復習と解説をしていきます。
まずはオブジェクト指向からです。

オブジェクト指向について復習しよう

C#のプログラミング学習を始めた時に、オブジェクト指向について学習したと思います。
オーバーライドを理解するためにも必要な知識になっていますので、ここで復習していきましょう。

オブジェクト指向プログラミングは「ものを組み立てるようにプログラミングする」事でした。

それぞれのオブジェクトに出来ること(役割)があり、それを組み合わせることで1つのプログラムにするという考え方がオブジェクト指向の基本の考え方です。

そして、オブジェクト(もの)の「できる事」を記す、オブジェクトの設計図が「クラス」と呼ばれます。みなさんもすでにクラスを作成しプログラミングしていると思うので、ここは想像できますよね。

ここまでの内容をもっと詳しく復習したい方は以下の記事を確認してみてください!図などを用いてオブジェクト指向について説明しています。

「クラス」を作る時、他のクラスからクラスの中の要素にアクセスできるかどうかをprivateやpublicを使って決めます。

このように、ほかのクラスからの操作で意図しない動きをしないように、必要な部分だけ外から使えるようにすることを「カプセル化」と呼びます。

「カプセル化」はオブジェクト指向の要素の一つで、ほかに「継承」「多態性」という要素があります。
さて、今回のオーバーライドを理解するためには、その「継承」「多態性」の考え方が大切になってきます!

早速、継承について学習していきましょう。

継承について学ぼう

プログラミングをしていて、同じような処理を何度も打ち込んでいませんか?
それは継承によって省略できるかもしれません。

「継承」とは、既存のクラス(設計図)から新しく作ったクラス(設計図)にメソッドなどを引き継ぐことです。すでにある設計図の内容を、新しく作った設計図でも使いたい時に使います。

簡単に例をみて確認していきましょう!

ここに「レンジを使って」「食べ物を温める」プログラムがあります。

中に「レンジに温めを指示する」メソッドを持った「人」クラスと、「食べ物を温める」メソッドを持った「レンジ」クラスが既に作成されていたとしましょう。

次に「レンジで温めた食品を使って料理をつくる」機能を追加することになりました。

レンジで食品を温める部分を継承してしまえば、「料理を作る」クラスだけ作成すればいいことになります。
もう完成している部分を使うので、安心ですし楽ちんですよね。

継承を使わなければ、もう一度レンジで食品を温める部分から作ることになります。

同じ動きをするものを再度作るのは無駄な感じがしますよね。

しかも、もし「レンジ」クラスに変更があったら、元の部分と新しく書いた部分の二か所を修正する必要があります。同じコードを何度も書くのは、修正漏れやミスの原因になりがちです。

このように、すでに完成しているクラスを再利用して、新しいクラスを作り上げることを「継承」といいます。いろいろなプログラムへ流用できる「拡張性」、修正箇所の少なさによるメンテナンスのしやすさが「継承」のメリットです。

「継承」では、継承されるクラスのことを「スーパークラス(親クラス)」といい、継承した新しいクラスのことを「サブクラス(子クラス)」と呼びます。

以上が継承の大切なポイントです。
オーバーライド理解のために必要な知識になるので、継承の仕組みと言葉は覚えておきましょう。

継承に関してもっと詳しく学習・復習したい方は、こちらの記事も合わせて確認してみてください。ゲームを例にわかりやすく継承を解説しています。

次はもう一つの要素、多態性について学習していきます!

多態性(ポリモーフィズム)について学ぼう

「多態性(ポリモーフィズム)」とは、メッセージの送信側とメッセージの受信側が動的に決まるというオブジェクト指向プログラミング言語が持つ性質のことです。

「多態性」って字からしてとっても難しそうですよね。紹介した基本の意味もどのような動きなのか理解しにくいです。「多態性(ポリモーフィズム)」が理解できるように、日常にあるもので考えてみましょう!

DVD、ブルーレイ、CDが再生できる機械が日常にはあります。

皆さんがどれかを再生したいと思ったときは、ディスクを入れて再生ボタンを押すだけです。でも、機械の中ではそれぞれのディスクによって違う読み込み方をして、再生しているはずですよね。

同じ機械で、同じ再生でもいろいろな動きをする。

これが「多態性(ポリモーフィズム)」の事です。

先程の説明に合ったメッセージの送信側が「入ってきたディスク」で、メッセージの受信側が「再生」にあたります。どんな「再生」になるかはディスクによって変わるということです。

さらに理解を深めるために、簡単にプログラミングに当てはめて考えてみましょう。同じメソッドだけど、メソッドを使用するオブジェクトによって動作を変えることが出来るということです。

このように、一つのメソッドに異なる動きをさせる事がオブジェクト指向の「多態性」です。

ここまで理解出来れば、オーバーライドも必ず理解できます。

なぜなら、オーバーライドも「多態性」の一つだからです!

次の章で、いったいどういうことなのか詳しく解説します!

オーバーライドとは

継承・多態性と密接な関係!

先の章で学習した「継承」では、共通の動きをコピーして再利用することができました。
ですが、コピーだけで同じ動作しかできませんでしたよね。

ですが「多態性」の考え方によって、再利用したものを目的に合わせてちょっとだけ違う動きをさせることが出来るとも学習しました。

「じゃあその「多態性」ってどうやって実現するんだろう…?」

ここまで理解できたみなさんは、そう考えたと思います。
実は、その実現方法が今回のテーマである「オーバーライド」なんです!

どんな方法で実現するのか、次の章で紹介していきます。

派生先のメソッドの書き替えることが出来る

前の章で、「継承」したクラスの「多態性」を実現するのが「オーバーライド」と紹介しました。
ここからは詳しく解説していきます。

継承した新しいクラス(サブクラス)では、もちろん継承元のクラス(スーパークラス)のメソッドを使うことが出来ます。その時、サブクラス上で新しくメソッドを作成することもできますが、スーパークラスのメソッドを上書きすることができます。

それが「オーバーライド」です。

「オーバー」上に + 「ライド」乗る(書く)
 

と覚えてしまいましょう!

スーパークラスのメソッドを上書きすることで、同じ名前のメソッドでも異なる動きをさせることが出来ます。

例を使って解説すると、以下のような感じです!

「電子機器」クラスに「電源を入れる」メソッドがあったとします。

その「電子機器」クラスを継承して、新しく「テレビ」クラスを作りました。

電子機器は電源を入れて使うものですが、電源の入れ方は様々ですよね。

新しく作った「テレビ」クラスでは、電源を入れる」メソッドを上書き(オーバーライド)してボタンを押す動作を追加しました。

同じ名前のメソッドでも、少しだけ違う動作をしていますよね。
オーバーライドはこのような機能です。

順番に理解していけば、オーバーライドもイメージしやすいかと思います。早速実際に使用してみたいところですが、しっかりメリットについても理解してから次の段階に進みましょう!

オーバーライドのメリットは?

ちょっと難しそうだからと避けがちなオーバーライドでしたが、ここまで学習した皆さんならメリットが理解できると思います。

オーバーライドの1つ目のメリットは、継承した部分を目的に応じた形に書き換えることで柔軟な開発が出来ることです。

スーパークラスのメソッドを再利用し、その結果を使ってオーバーライドすることもできます。もちろん、内容すべて上書きしてしまっても問題ありません。

サブクラスでの機能の追加や変更が非常に簡単に出来るということです。

2つ目のメリットは、修正が必要になった時の修正範囲が小さくなることです。

スーパークラスのメソッドを再利用してオーバーライドしていれば、サブクラスのメソッドは修正しなくてよくなります。修正する箇所が少ないほうが、修正漏れを防ぐことが出来るのでメンテナンスがしやすいですよね。

コードは、書いた本人ではない他人が修正することもあります。
そのためにも、メンテナンスのしやすさ、コードの読みやすさについては常に気を配る必要があるのです。

継承のメリットと重なる部分が多いですが、継承だけではここまで柔軟な開発はできません。オーバーライドがあることによって、さらに開発が楽になりミスが減らせるということですね。

混同しがちな「オーバーロード」との違い

さて、今まで「オーバーライド」を検索したことのある方は、この章の題名である「オーバーロード」を見かけたことがあると思います。字がとっても似ていますよね!

ですが、意味は異なりますので混同しないように注意しましょう。

「オーバーロード」は引数の数や型、返り値が異なるメソッドを同じ名前で複数定義することです。継承をつかったりせず、同じクラス内で同じ名前のメソッドを作ることをいいます。
「オーバーライド」はサブクラスで、スーパークラスのメソッドを上書きすることでした。

同じクラス内で、違う動きのする同じ名前のメソッドを作るのが「オーバーロード」です。

別のクラスで、メソッドを上書きするのが「オーバーライド」です。

同じクラスなのか、別のクラスなのかが非常に重要なポイントです。名前は似ていますが、中身は全く違う事なので混ぜて覚えないように注意しましょう!

実際にオーバーライドを使ってみよう

さて、ここまででかなりオーバーライドについて理解が深まったと思います。
ここからは実際に使用するためのポイントと、具体的なコードを紹介します。

まず2つのキーワードを覚えよう!

オーバーライドを実際に使うために、まずは簡単に2つのキーワードから覚えましょう!

一つ目は「virtual」です。

これを付けたメソッドはオーバーライド(上書き)していいよという印です。
スーパークラスのメソッドに付けます。もちろん、サブクラスでオーバーライドせずそのまま使っても構いません。

「virtual」を付け、オーバーライドを許可したメソッドのことを「仮想メソッド」と呼びます。

二つ目は「override」です。

このメソッドはオーバーライド(上書き)しますよ、という宣言のようなものです。
サブクラスのメソッドに付けます。これを付けなければ、オーバーライドすることが出来ません。

以上をまとめると、スーパークラスで「virtual」がついているメソッドは、サブクラスで「override」を付けてオーバーライドしてよいということになります。

実装はこの二つの言葉を使うだけでできますので、さっそく実際のコードを見てみましょう!

キーワードを使ってオーバーライドしてみよう

早速オーバーライドしてみます!今回は現在の時刻を文字列で返すメソッドをオーバーライドして、あいさつだけ表示するクラス、時間とあいさつを表示するクラスを作ってみます。

using System;

namespace Sample
{
    //スーパークラス
    class SuperClass{

        private DateTime now;

        //初期化
        public SuperClass()
        {
            now = new DateTime();
        }

        //時刻を文字列で返す
        public virtual string Message()
        {
            now = DateTime.Now;
            return now.ToString();
        }

    }

    //サブクラス1つ目
    class SubClass1 : SuperClass
    {
        //スーパークラスのMessageメソッドをオーバーライド。
        public override string Message()
        {
            return "こんにちは!!";
        }
    }

    //サブクラス2つ目
    class SubClass2 : SuperClass
    {
        //スーパークラスのMessageメソッドも使い、オーバーライドする。
        public override string Message()
        {
            return base.Message() +  " こんにちは!!";
        }
    }

    class OverrideTest
    {
        static void Main(string[] args)
        {

            SuperClass sp = new SuperClass();
            SubClass1 sb1 = new SubClass1();
            SubClass2 sb2 = new SubClass2();

            //スーパークラスの結果
            Console.WriteLine(sp.Message());
            
            //サブクラス1の結果
            Console.WriteLine(sb1.Message());
            
            //サブクラス2の結果
            Console.WriteLine(sb2.Message());

            Console.ReadKey();

        }
    }
}

 

実行結果:

2019/02/18 15:37:21
こんにちは!!
2019/02/18 15:37:21 こんにちは!!

スーパークラスでは日時を文字列にし返す「Message」メソッドを作りました。その際、オーバーライドしても良いように先程紹介した「virtual」という言葉を付けています。

サブクラス1は、スーパークラスを継承しています。そのままだと同じように日時を文字列を返すだけですが、「override」を付けて「Message」メソッドをオーバーライドしています。

オーバーライドすることによって、日時を返すのではなく、挨拶を返すことにしました。結果が挨拶のみになっている事から、オーバーライドが成功しているのがわかります。

サブクラス2も、スーパークラスを継承しています。こちらも、「override」を付けて「Message」メソッドをオーバーライドしています。

ですが、「base.Message」という記述があるのがわかるでしょうか?

これはスーパークラスの「Message」メソッドを呼び出す命令です。オーバーライドのメリットを説明した時「スーパークラスのメソッドを再利用し、その結果を使ってオーバーライドすることもできます。」と紹介しました。

結果は、日時と挨拶になっています。スーパークラスの「Message」の結果 + 挨拶 のオーバライドが成功していますね。

以上が、基本の実装方法です!
スーパークラスで「virtual」を付け、サブクラスで「override」を付けて上書きする内容について記述するだけでオーバーライドは実装できます。

オーバーライドについて、段階を経て実装まで解説しました。
ちょっと仕組みのイメージがしづらいだけで、考え方としてはそこまで難しくないのがオーバーライドです。使ってみないと出来るようにならないと思いますので、このサンプルを参考にいろいろ試して慣れていってくださいね!

必ずオーバーライドするメソッドを作りたい時は

少しオーバーライドについて、慣れてきたでしょうか?ここからは少しだけ応用編になります。
まず始めに紹介するのは、必ずオーバーライドしてほしい時の対処法です。

抽象クラス・メソッドについて学ぼう

必ずオーバーライドするメソッドを作る前に、知っておくべき知識があります。
それが「抽象クラス」と「抽象メソッド」です。
「抽象」という言葉の通り、あいまいで具体的ではないということですが、ちょっとわかりにくいですよね。

「抽象メソッド」はメソッドの名前と引数の型、数、戻り値の型だけを定義したメソッドのことです。メソッドの中には具体的な処理を記載してはいけません、記載するとエラーになります。

「抽象クラス」は「抽象メソッド」が一つ以上含まれているクラスです。何も処理が記載されていないメソッドがあるので、そのまま使用することが出来ません。

エラーになるコードを一度見てみましょう

namespace Sample
{
    //スーパークラス
    abstract class dog
    {

        private string dogname;

        //初期化
        public dog(string dogname)
        {
            this.dogname = dogname;
        }

        //名前の取得
        public string DogName
        {
            get { return dogname; }
        }

        //吠えるメソッド
        public abstract string Bark()
        {
            Console.WriteLine("わんわん");
        }
    }
}

「犬」の抽象クラスを作成しました。

「吠える」抽象メソッドの部分を見てください。
書いてみるとわかりますが、「abstract に指定されているため本体を宣言できません」とコンパイルエラーがでます。このように、メソッドの中身は一切記載してはいけません。

そして、抽象メソッドの中身はサブクラスの中で必ず定義しなければなりません。定義しないと、これもまたエラーになります。

namespace Sample
{
    //スーパークラス
    abstract class dog
    {

        private string dogname;

        //初期化
        public dog(string dogname)
        {
            this.dogname = dogname;
        }

        //名前の取得
        public string DogName
        {
            get { return dogname; }
        }

        //吠えるメソッド
        public abstract string Bark();

        //チワワクラス
        class Chihuahua : dog
        {
            //初期化
            public Chihuahua() : base("チワワ")
            {
            }
        }

    }
}

このコードはコンパイルエラーが発生します。チワワクラスで「吠える」メソッドを作っていないためです。

もちろん、継承したメソッドの中身を定義するには、オーバーライドを使います。

「抽象クラス」と「抽象メソッド」にはこのように、継承先での実装漏れを防ぐ効果があります。
クラスのひな形をつくるとイメージしても構いません。

ひな形があれば、実装漏れを防ぐことができたり、メソッド名の統一ができたりします。複数人で開発する際には、非常に効果的です。

「抽象クラス」と「抽象メソッド」について何となく理解できたでしょうか?短時間で細部まで理解するのは難しいと思いますが

「抽象クラス」は必ず継承しなければ使えない
 
「抽象メソッド」は必ずオーバーライドしなければならない

この二点だけはしっかり覚えてくださいね!

それでは具体的な作り方について、次の章で解説していきます。

実際に抽象クラス・メソッドを作ってみよう

さっそく実践していきましょう。

今回は先ほどの例であった「犬」クラスを作り、「吠える」メソッドを必ずオーバーライドするようにします。
その「犬」クラスを継承した「チワワ」クラスと「ゴールデンレトリバー」クラスを作成して、コンソールにそれぞれの鳴き声を表示させます!

抽象クラスや、メソッドを作るには「abstract」という言葉を付けることで簡単にできます。

using System;

namespace Sample
{
    //スーパークラス
    abstract class dog{

        private string dogname;

        //初期化
        public dog(string dogname)
        {
            this.dogname = dogname;
        }

        //名前の取得
        public string DogName
        {
            get { return dogname; }
        }

        //吠えるメソッド
        public abstract string Bark();

    }

    //チワワクラス
    class Chihuahua : dog
    {
        //初期化
        public Chihuahua() : base("チワワ")
        {
        }

        //スーパークラスのBarkメソッドをオーバーライド。
        public override string Bark()
        {
            return "きゃんきゃん!";
        }
    }

    //ゴールデンレトリバークラス
    class Golden : dog
    {
        //初期化
        public Golden() : base("ゴールデンレトリバー")
        { 
        }

        ////スーパークラスのBarkメソッドをオーバーライド。
        public override string Bark()
        {
            return "わんわん!";
        }
    }

    class OverrideTest
    {
        static void Main(string[] args)
        {

            Chihuahua chi = new Chihuahua();
            Golden gol = new Golden();
           
            //チワワの結果
            Console.Write(chi.DogName + @"「");
            Console.WriteLine(chi.Bark() + @"」");

            //ゴールデンレトリバーの結果
            Console.Write(gol.DogName + @"「");
            Console.WriteLine(gol.Bark() + @"」");

            Console.ReadKey();

        }
    }
}

 
実行結果:

チワワ「きゃんきゃん!」
ゴールデンレトリバー「わんわん!」

サンプルコードのスーパークラスに「abstract」がついているのがわかりますね!この「abstract」によって、抽象クラス、メソッドであると宣言できます。

抽象メソッドを一つでも持つクラスは、必ず抽象クラスにしないとエラーになるので注意しましょう。

また、抽象メソッドは、それぞれのサブクラスで内容を書くので、スーパークラスではなにも記載できない点に関しても注意が必要です。

以上で、必ずオーバーライドするメソッドが作れました!

抽象と考えると少し難しいですが、共通的な要素がたくさんあるけど、動作はそれぞれ違うものにしたい時には非常に便利です。また、オーバーライド忘れがなく不具合を防ぐこともでき、安心ですね。

逆に、オーバライドをしてほしくないときはどうしたらよいのかを次の章で開設していきます!

派生クラスでオーバーライドを禁止したい時は

先程は、必ずオーバーライドするメソッドを作りました。
この章では、sealed を使ってオーバーライドを禁止する方法について紹介します。

sealed 修飾子を使ってシールメソッドを作ってみよう

スーパークラスのメソッドをオーバーライドしてほしくないときは、「virtual」を付けなければオーバーライドできません。この「sealed」はサブクラスのサブクラスでオーバーライドを禁止する時に使用します。

先程の、日時とあいさつを返すサンプルコードを少し編集してみてみましょう。

    //スーパークラス
    class SuperClass
    {

        private DateTime now;

        //初期化
        public SuperClass()
        {
            now = new DateTime();
        }

        //時刻を文字列で返す
        public virtual string Message()
        {
            now = DateTime.Now;
            return now.ToString();
        }

    }

    //サブクラス1つ目
    class SubClass1 : SuperClass
    {
        //スーパークラスのMessageメソッドをオーバーライド。
        public sealed override string Message()
        {
            return "こんにちは!!";
        }
    }

    //サブクラス2つ目
    class SubClass2 : SubClass1
    {
        //スーパークラスのMessageメソッドも使い、オーバーライドする。
        public override string Message()
        {
            return base.Message() + " こんにちは!!";
        }
    }

サブクラス1の「Message」メソッドに「sealed」がついていますね。そしてサブクラス2はサブクラス1を継承し、「Message」メソッドをオーバーライドしようとしています。

ですが、サブクラス1に「sealed」がついているので、オーバーライドできませんとエラーが表示されます。

このように、派生先のさらに次の階層でオーバーライドを禁止することができます。あまり使うことは無いかもしれませんが、覚えておいて損はないテクニックなので合わせて学習しましょう!

まとめ

オブジェクト指向の復習から、オーバーライドを説明してきました。
皆さん理解できたでしょうか?

難しく考えがちなオーバーライドですが、基礎から順番に勉強していけば確実に理解できます。もし、不安な要素があればオブジェクト指向の部分から再度確認していってくださいね。

それではまた次の解説でお会いしましょう!

この記事を書いた人

文系大学出身、なんとか自力で頑張りプログラマー歴今年で8年目。
自力で頑張って勉強した経験を生かし、読者の皆様に分かりやすく親しみやすい記事を書けるよう日々邁進中です。
出来る言語はC#,VB,Java,Delphiなどなど、幅広く触っています。

目次