🐚

[魚戸ホタル] C#勉強備忘録5日目-2

2022/04/03に公開

おはようございます
こんにちは
ほたー

前記事の続きになります。
前記事は下リンク
https://zenn.dev/uotohotaru/articles/74cadd13d2dc4f

さて前記事の最初に5日目の備忘録のテーマとして以下の6点を掲げていました

使用教材は亀田健司(著)1週間でC#の基礎が学べる本になります。
テーマに掲げた5点のうち1~3を前記事で紹介したので、本記事で4~6を紹介します。

対象ページは以下の通りです。
4. 265~274
5. 275~277
6. 277~296

C#備忘録アーカイブ

C#における抽象クラス

前半で説明したようにクラスは既存のクラスを 継承 して作ることが可能です。
本章では継承の考え方を発展させた 継承クラス を紹介します。

継承クラスを用いたプログラムの例を以下に書きました。
兵庫県の市名と市の花の名前をコンソールに出力するプログラムです。
明石市ともくもく会で行った川西市を例に用いました。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_15
{
    internal class Program
    {
        static void Main(string[] args)
        {
            HyogoPrefecture[] hyogo_cities = new HyogoPrefecture[2];
            hyogo_cities[0] = new Akashi();     // 明石市クラス
            hyogo_cities[1] = new Kawanishi();  // 川西市クラス
            for (int i = 0; i < hyogo_cities.Length; i++)
            {
                hyogo_cities[i].ShowCityName();
                hyogo_cities[i].ShowFlowerName();
                Console.WriteLine();
            }

        }
    }
}
Akashi.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_15
{
    internal class Akashi : HyogoPrefecture
    {
        // コンストラクタ
        public Akashi() : base("明石市") { }
        // 市の花の名前を表示
        public override void ShowFlowerName()
        {
            Console.WriteLine("きく");
        }
    }
}
Kawanishi.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_15
{
    internal class Kawanishi : HyogoPrefecture
    {
        // コンストラクタ
        public Kawanishi() : base("川西市") { }
        // 市の花の名前を表示
        public override void ShowFlowerName()
        {
            Console.WriteLine("りんどう");
        }
    }
}
HyogoPrefecture.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_15
{
    abstract class HyogoPrefecture
    {
        // 市名
        private string cityname;
        // 引数付きコンストラクタ
        public HyogoPrefecture(string cityname)
        {
            this.cityname = cityname;
        }
        // 市の花の名前を表示
        public abstract void ShowFlowerName();
        // 市名を表示
        public void ShowCityName()
        {
            Console.WriteLine("市名:{0}", cityname);
        }

    }
}

実行結果

実行結果から明石市と川西市の市名と市の花の名前を出力することができました。
明石市の市の花である きく については毎年10月~11月にかけて明石公園で展覧会が行われ、実際に訪れて写真を撮影することができます。
鮮やかで特徴がでており、毎年の楽しみです。知っている先生が寄贈していたこともございました。
川西ではりんどうの季節ではなかったので見ることはできませんでしたが、桜が咲いており綺麗でした。

花の話はここまでにして、抽象クラスの機能および書いたプログラムについて説明します。

書いたプログラムの流れについて、
まずHyogoPrefectureクラスから大きさ2の配列を作成します。
次に配列の中に生成したAkashiクラスとKawanishiクラスを入れます。
このときにAkashiクラスとKawanishiクラスで記述したコンストラクタが働き、HyogoPrefectureクラスで記述したコンストラクタに市名が引数で渡され、変数citynameに市名が代入されます。
最後に繰り返し処理の中でShowCityNameメソッド、ShowFlowerNameメソッドを実行し市名、市の花の名前をコンソールに出力します。

明石市と川西市は兵庫県にあるという共通点を集約して作ったベースクラスがHyogoPrefectureクラスです。
HyogoPrefectureクラスを定義する前には abstract修飾子 が用いられていますが、これが抽象クラスであることの目印になります。

抽象クラスは インスタンスを生成できない という特徴があります。
明石市と川西市という市名はございますが、兵庫県という市名は存在しません。
つまりインスタンスを作るには「兵庫県」を継承して「明石市」や「川西市」などの実在する市名のクラスを作る必要があります。

抽象クラスを作る目的は 継承してサブクラスを作る という設計を強制することにあります。

またHyogoPrefecture.csでShowFlowerName()というメソッドが定義されていますが、abstract修飾子が付いています。
このようなメソッドを 抽象メソッド といいます。
抽象メソッドをサブクラスで実装する場合はoverride修飾子とつける必要があります。

上記のプログラムにおけるShowFlowerName()メソッドについてはHyogoPrefectureクラスでは時に処理はなく、AkashiクラスとKawanishiクラスでオーバーライドして「きく」、「りんどう」とそれぞれ別の値を出力しています。

以上のように抽象クラスはアレンジの見本を作りだす機能と言えます。
ちょうど自分自身が教材を見本にZennでC#勉強備忘録を書いているようなものです。

技術を習得するには自分の言葉で練り上げる必要があります。
そのまま例題を移しただけであれば教材の基になった一週間で身につくC#言語の基本を見るだけでいいです。
自分の学習に意味を持たせたい そう思いました。

次はプロパティにも抽象プロパティがあるということで紹介します。

C#における抽象プロパティ

抽象クラスで利用できるのは抽象メソッドだけではございません。
プロパティも抽象プロパティを持つことができます。

抽象プロパティを用いたプログラムの例を以下に書きました。
三角錐と四角錘の体積を求めるプログラムです。
三角錐と四角錘の体積は底面積 * 高さ * 1/3で求めることができます。
体積を求めるのに必要な幅、長さ、高さのフィールド名をそれぞれwidth、len、heightとしました。
長さをlengthにしなかったのは文字列の長さを取得するString.Lengthというメソッドが存在するためです。

CalcBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_13
{
    // スーパークラス
    abstract class CalcBase
    {
        // 抽象プロパティ
        // 幅
        public abstract double Width
        {
            set; get;
        }
        // 長さ
        public abstract double Len
        {
            set; get;
        }
        // 高さ
        public abstract double Height
        {
            set; get;
        }

    }
}
Calc.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_13
{
  // サブクラス
    internal class Calc : CalcBase
    {
        private double width = 0.0;
        private double len = 0.0;
        private double height = 0.0;
        // プロパティの実装
        public override double Width
        {
            set { width = value; }
            get { return width; }
        }
        public override double Len
        {
            set { len = value; }
            get { return len; }
        }
        public override double Height
        {
            set { height = value; }
            get { return height; }
        }
    }
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_13
{
    // 抽象プロパティの例
    internal class Program
    {
        static void Main(string[] args)
        {
            Calc c = new Calc();
            c.Width = 10.0;
            c.Len = 6.0;
            c.Height = 20.0;
            Console.WriteLine("三角錐の体積:{0}cm3", ((c.Width * c.Len) / 2) * c.Height / 3);
            Console.WriteLine("四角錘の体積:{0}cm3", (c.Width * c.Len) * c.Height / 3);
        }
    }
}


実行結果

実行結果から三角錐と四角錘の体積を求めて出力することができました。
Calcクラスは抽象クラスCalcBaseを実装しています。
抽象プロパティも抽象メソッドと同様にabstract修飾子を付けています。
中身にはsetメソッドおよびgetメソッドを書いており、特に実装は行っていません。
実際に実装を行い、変数width、len、heightに値を代入しているのががCalcクラスです。

メソッドと同様にプロパティにもoverrideをつければ、プロパティとして使用できることがわかりました。
次は例えばヒトの腕だと投げる、掴む、持ち上げる等の複数の動作がございます。
そのような複数の機能をプログラムとして実装しなくてはならないときに役立つインターフェースと呼ばれる機能について紹介します。

C#におけるインターフェース

抽象クラスでは自分自身を継承したサブクラスを複製することにより、できる機能の拡張を行っていました。
それでは例えば物流において配達物の積み込みと荷届先への配達を別々の人が担当するといったような、機能の実行に制限をつけたい場合はどのようにすればよいか?

このような機能の制限に役立つのがインターフェースです。

インターフェースを用いたプログラムの例を以下に書きました。
例えば2.5Kgの鉄球があると仮定して、投げる・持ち上げる動作をすると考えます。
鉄球を握力強化のために持ち上げる方もいれば、競技として投擲する方もいるという違いがあります。
投げる・持ち上げる動作を分割してインターフェースとし、それぞれIPitchインターフェース、ILiftインターフェースとしました。
ベースクラスはArm(腕)クラスと命名し、ArmクラスでIPitch・ILiftインターフェースを呼び出すようにプログラムを組みました。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_14
{
    internal class Program
    {

        static void Main(string[] args)
        {
            Arm arm = new Arm("鉄球", 2.5);
            // IPitchインターフェースでインスタンスにアクセス
            IPitch pitch = (IPitch)arm;
            pitch.Pitching();
            /*
             * liftingメソッドは利用できない
             * pitch Lifting();
             */

            // ILiftインターフェースでインスタンスにアクセス
            ILift lift = (ILift)arm;
            lift.Lifting();
            /*
             * pitchingメソッドは利用できない
             * lift Pitching();
             */
        }
    }
}
Arm.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_14
{
    internal class Arm : IPitch, ILift
    {
        // 投げる・持ち上げる対象の名前
        private string name;
        // 対象の重さ(kg)
        private double weight;
        // コンストラクタ
        public Arm(string name, double weight)
        {
            this.name = name;
            this.weight = weight;
        }
        // 対象を投げるメソッド
        public void Pitching()
        {
            Console.WriteLine(this.weight + "kgの" + this.name + "を投げました。");
        }
        // 対象を持ち上げるメソッド
        public void Lifting()
        {
            Console.WriteLine(this.weight + "kgの" + this.name + "を持ち上げました。");
        }

    }
}
IPitch.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_14
{
    internal interface IPitch
    {
        // 対象物を投げる
        void Pitching();
    }
}
ILift.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_14
{
    internal interface ILift
    {
        // 対象物を持ち上げる
        void Lifting();
    }
}

実行結果

実行結果から2.5Kgの鉄球を投げる・持ち上げる動作を分けて出力することができました。

それではインターフェースの機能および書いたプログラムについて説明します。

書いたプログラムの流れについて、
まずArmインスタンスを定義して、引数に持ち上げる対象の名前と重さを入れることでコンストラクタの働きにより変数に値を代入します。
次にIPitchインターフェース、ILiftインターフェースそれぞれでArmインスタンスにアクセスします。
最後にPitchingメソッド、Liftingメソッドを実行し、コンソールに実行結果を表示します。

インターフェースは以下の書式になります。

インターフェースの役割は抽象プロパティと似ており、インターフェース内で定義されたメソッドをクラス内で実装する必要があります。
ただし インターフェースにはメソッドの実装や、フィールドを持つことはできません。
インターフェース単体では何の働きもせず、他のクラスで実装して機能を生かすことができます。
インターフェース内で宣言するメソッド・プロパティはすべてpublicであることが前提です。
またこのインターフェースを実装するクラス内で該当するメソッドに対してabstractやoverrideをつける必要はございません。
インターフェースの命名は「IPitch」「ILift」などといったようにIから始めるのがC#言語のプログラミングの慣習だそうです。

継承の場合とは異なり、インターフェースは複数実装することができます。
例題ではArmクラスにIPitch、ILiftインターフェースを実装しています。

ArmクラスでPitchingメソッドとLiftingメソッドが記述されているのになぜインターフェースを実装する必要があるのでしょうか?
その理由はインターフェースを実装することにより架空のクラスになることができるためです。
インターフェースをインスタンスにアクセスさせる際にはキャストを使用します。
Program.csではIPitchでは(Ipitch)というキャストをつけてarmに代入しています。
IPitch pitch = (IPitch)arm;

できたクラスを仮にIPitchクラスとすると、インターフェースにあるPitchingメソッドを呼び出せばコンソールに「2.5Kgの鉄球を投げました。」と表示されます。
ただしIPitchクラスにはLiftingメソッドが存在しないため、呼び出そうとするとエラーになります。

インターフェースには他にもインターフェース同士でメソッドの重複があってもかまわないという特徴があります。

上記のように利用する機能に制限をつけることができるのがインターフェースの利点です。
機能を制限することで例えば食べるメソッドを腕クラスで実行するような間違ったアクセスを防ぐことができます。

最後に

魚戸ホタル軸のアプリ作成をするには基礎的な文法を学び、整理する必要がありました。
だからこそ 教材の内容を学習した記録をつけて、やり遂げようと決めました。
5日目の内容は一番整理に時間がかかり、なかなかストレスがかかりました。
その分、5日目の内容がプログラムの組み立てをより簡単にするために必要なんだよなあ と腑に落ちました。

ありがとうございました。Thank you。

Discussion