クラスの継承で機能を拡張すればめちゃくちゃ便利!

2023年2月7日

こんにちは、ノムノムです。

今回はクラスの継承を行います。

クラスの継承とは、基底クラスの機能を引き継いで新しいクラスを作るということです。

ゲームに例えると例えると騎士の能力を引き継いで勇者にクラスアップするというようなイメージです。

acworksさんによるイラストACからのイラスト

要は基底となるクラスが持っている関数や変数の情報をもったまま新しいクラスを作ることができるということですね。

クラスの継承の仕方

C#でWindowsアプリを作っているかたであれば一度は目にしたことがあります。

以下の部分です。

public partial class Form1 : Form

そうです。フォームを作成すると自動でFormクラスを継承した新しいクラスを作成してくれます。
この場合、Formクラスを継承したForm1クラスということになります。

partialというのは部分的にという意味で、つまりForm1クラスは他のファイルにも機能が書かれている可能性がありますよっていうことです。
この場合フォームなので「Form1.Designer.cs」がそれにあたります。

どんなときに継承するのか

さてクラスの継承で機能を引き継げるのはわかりました。ではどのような場合に使用するでしょう。

クラスの継承は、同様の機能をもったクラスを複数作る必要がある場合利用します。

例えば、binファイルを出力する関数をもったクラスを2つ作りたいと思います。
両方ともbinファイルを出力するのに同じ機能を使います。

binファイル出力機能を基底クラスに持たせ、それを継承したクラスを2つ作ります。

class MyBaseClass
{
    public bool OutputBinary(string file, Byte[] binary)
    {
        ・・・・処理・・・・
        return true;
    }
}
class MyAClass : MyBaseClass
{
}
class MyBClass : MyBaseClass
{
}

これでMyAClassクラスとMyBClassクラスはOutputBinary関数を利用することができます。

継承元となったクラスはスーパークラス(基底クラス)と呼ばれます。

継承元の関数を書き換えたいときはオーバーライドする

クラスの継承を使っているとどうしても継承元の関数では不便なことが出てきます。

そんなときはオーバーライドつまり上書きです。

上記の例でいえばOutputBinary関数でMyBClassクラスから呼ばれた時はファイル参照ダイアログを出したいとか思った時にオーバーライドが使えます。

記述方法を説明します。まず基底クラス(MyBaseClassクラス)です。

public virtual bool OutputBinary(string file, Byte[] binary)
{
    return true;
}

1行目の宣言部分にvirtualと足します。意味としては仮想です。オーバーライドされなかったら呼ばれる関数だよと宣言しています。

次に派生先クラス(MyBClassクラス)の方です。

public override bool OutputBinary(string file, Byte[] binary)
{
    //ダイアログ表示
    using (System.Windows.Forms.SaveFileDialog dlg = new System.Windows.Forms.SaveFileDialog())
    {
        dlg.FileName = file;
        dlg.Filter = "BINファイル(*.bin)|*.bin|すべてのファイル(*.*)|*.*";
        dlg.Title = "BINファイル指定";     //タイトルを設定する

        //ダイアログを開く
        if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK)
        {
            //OKが返ってこない場合は、キャンセルされたとする。
            file = "";
            return false;
        }

        //選択したファイル名を取得する
        file = dlg.FileName;
    }

    //ダイアログ表示後は基底クラスを呼ぶ
    return base.OutputBinary(file, binary);
}

ポイントは1行目の関数部分にoverrideというステートを追加しています。
これはこの関数は基底クラスにある関数を上書きするという意味です。

もう一つのポイントは最後23行目「base.OutputBinary(file, binary)」の部分です。
このbaseというのは基底クラスを指します。
virtualで宣言している基底クラス( MyBaseClassクラス)の方の OutputBinary関数を呼び出しています。

派生先に絶対に宣言してもらいたい関数があるとき

派生先クラスの呼び出し元で必ず呼び出されるが、呼び出されるクラスごとで処理が変わるなどといった場合は、基底クラスにabstractを追加することで抽象化できます。

つまり、宣言だけして処理は派生先のクラスにお任せしますという意味です。

それでは、必ず初期化するように例を変えていこうと思います。

abstract class MyBaseClass
{
    public virtual bool OutputBinary(string file, Byte[] binary)
    {
        return true;
    }

    public abstract void Initilize();        //初期化
}

ポイントは1行目と8行目ですね。抽象化した関数を宣言をするにはabstractを付けます。

現在抽象化された関数の実体がないのでエラーが出てます。

これで必ずInitilize関数をそれぞれのクラスで作らなくてはいけなくなりました。

class MyAClass : MyBaseClass
{
    public override void Initilize()
    {
        //MyAClass用の初期化処理
    }
}
class MyBClass : MyBaseClass
{
    public override void Initilize()
    {
        //MyBClass用の初期化処理
    }

    public override bool OutputBinary(string file, Byte[] binary)
    {
        //ダイアログ表示
        using (System.Windows.Forms.SaveFileDialog dlg = new System.Windows.Forms.SaveFileDialog())
        {
            dlg.FileName = file;
            dlg.Filter = "BINファイル(*.bin)|*.bin|すべてのファイル(*.*)|*.*";
            dlg.Title = "BINファイル指定";     //タイトルを設定する

            //ダイアログを開く
            if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK)
            {
                //OKが返ってこない場合は、キャンセルされたとする。
                file = "";
                return false;
            }

            //選択したファイル名を取得する
            file = dlg.FileName;
        }

        //ダイアログ表示後は基底クラスを呼ぶ
        return base.OutputBinary(file, binary);
    }

}

これでInitilize関数の実装し忘れを防げますね。

まとめ

今回は、クラスの継承について説明しました。

クラスの継承をうまく使えば同じ処理を何度も書く必要がなくなります。
汎用性も高くなるのでぜひ習得して頂きたいですね。

基底クラスは、継承する元ということで汎用性が高いクラスになります。
つまり、自分用の基底クラスがたくさんできればできるほどそれは資産になります。

資産は使いまわせます。絶対に他のプログラムでも作った基底クラスを再利用できる時が来ます。
たくさん作って行きましょう。

ここまで読んで頂いてありがとうございました。