🐚

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

2022/04/03に公開

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

5日目の備忘録は主なテーマは以下の6点です。

使用教材は亀田健司(著)1週間でC#の基礎が学べる本になります。
テーマに掲げた5点のうち1~3を本ページ、4~6を[魚戸ホタル]C#勉強備忘録5日目-2で紹介します。
1記事10000文字を超えると多いな~と感じてしまうので、これから技術記事を書く時も なんとか10000文字以内に収められないか と考えます。

対象ページは以下の通りです。
1:230~248
2:249~258
3:259~264

C#備忘録アーカイブ

C#における静的メンバ

4日目のオブジェクト指向の学習では新しいCSファイルからインスタンスを生成し、Program.csで呼び出した後にプログラムの処理を実行していました。
本日はインスタンスを生成しなくても名前を呼ぶだけで処理を実行できるフィールド/メソッドを紹介します。
ずばり言うと 静的メンバ です。
フィールドやメソッドを定義するときに static(スタティック)修飾子をつけると静的メンバになります。

静的メンバを用いたプログラムの例を以下に書きました。
Mathクラスの勉強も兼ねて、小数点以下の数値の操作に使われるメソッドの実行結果を出力するプログラムを作成しました。
小数点以下の数値の処理はお金を扱うときに必要になると思います。

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

namespace UotoHOtaru0402_12
{   

    internal class Program
    {
        
        static void Main(string[] args)
        {
            Console.WriteLine("数字を入力してください。小数点以下を切り捨て、切り上げした値を表示します");
            Calc c = new Calc();
            Console.Write("数字(元の値)=");
            c.Num1 = double.Parse(Console.ReadLine());
            c.Floor();
            Calc.Truncate();
            Calc.Ceiling();
        }
    }
}

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

namespace UotoHOtaru0402_12
{
    internal class Calc
    {
        // num1フィールド(静的)
        private static double num1;
        // num1プロパティ
        public double Num1
        {
            set { num1 = value; }
            get { return num1; }
        }
        // Floorメソッド(インスタンス)
        public void Floor()
        {
            Console.WriteLine("Math.Floorの実行結果:{0}", Math.Floor(num1));
        }
        // Truncateメソッド(静的)
        public static void Truncate()
        {
            Console.WriteLine("Math.Truncateの実行結果:{0}", Math.Truncate(num1));
        }
        // Ceilingメソッド(静的)
        public static void Ceiling()
        {
            Console.WriteLine("Math.Ceilingの実行結果:{0}", Math.Ceiling(num1));
        }
    }
}

実行結果(正の値)

実行結果(負の値)

実行結果から元の値から小数点以下を切り捨て、切り上げした値を表示することができました。
まず入力した値をプロパティに入れて変数num1に設定します。
次にFloorメソッド、Truncateメソッド、Ceilingメソッドを呼び出してMath.Floor()、Math.Truncate()、Math.Ceiling()の実行結果を表示します。

その際にFloorメソッドはc.Floor()、TruncateメソッドはCalc.Truncate()の形で呼び出していますが、これが4日目までに実行してきた通常のメソッドと静的メソッドの違いです。

通常別クラスで定義したメソッドは一旦インスタンスを生成してから呼び出す必要がございました。
しかし静的メソッドはわざわざインスタンスを生成しなくても呼び出すと実行できます。
静的メソッドを呼び出すときは[クラス名、メソッド名(引数)]という形で書きます。
また区別のためインスタンスを生成しないと呼び出せないメソッドについては インスタンスメソッド と呼ばれます。

次にCalcクラスにあるnum1フィールドについて、staticが付いているためこちらもインスタンス生成なしに利用できます。
このようなフィールドを静的フィールドと呼び、静的メソッドと同じように[クラス名、フィールド名]という書式で利用します。
staticがない場合はthis.num1という書き方になります。
thisは自身のクラス、つまりCalcクラスのことです。

最後にFloorメソッド、Truncateメソッド、Ceilingメソッドについて書きます。
実行されるメソッドは以下のような働きをします

  • Math.Floor:「現在の値以下で最大の整数値」になるように小数点以下切り捨て
  • Math.Truncate:単純に小数点以下を切り捨て
  • Math.Ceiling:「現在の値以上で最小の整数値」になるように小数点以下切り上げ
    上記の働きによって正の値、負の値を入力したときの値がそれぞれのメソッドで異なるようになっています。

他に主に使われるMathクラスはこちらの技術記事にまとめられています。

静的メンバのほかに本章で紹介されたポイントを箇条書きで整理しました。

  • Mainメソッドは静的なメソッドのうちプログラム実行時に呼び出される処理が記述される特殊なメソッドであり、プログラムの実行開始地点という意味で エントリーポイント と呼ばれる。
  • 特性としてインスタンスメンバから静的メンバにアクセスすることは可能。その逆に静的メンバからインスタンスメンバに直接アクセスすることは不可能
  • ネームスペースが異なるクラスをゲストで呼び出したい場合、ホスト側でusingディレクティブを用いて名指しする必要がある
  • ConsoleクラスはSystemネームスペースに含まれ、省略すると以下のように記述する必要がある
    System.Console.WriteLine("Hello");
  • メソッドを呼び出すことができる範囲を スコープ(Scope) と呼び、スコープ内で定義した変数のことを ローカル変数 と呼ぶ
  • スコープの考えでは{}で囲まれた範囲から外れると存在していないことになる

C#におけるクラス継承

クラスには機能を拡張する仕組みがあります。
その中で最も代表的なものがC#だけでなく、ほとんどのオブジェクト指向言語に備わっている継承(インヘリタンス)です。

継承を用いたプログラムの例を以下に書きました。
機能紹介のため単純にコンソールに文字列を出力するプログラムです。
まずMainメソッドでイカが何匹か入力します。
次にSquid(イカ)クラスでSwimメソッド・Spitメソッドを定義し「n匹のsが泳いだ」、「n匹のsが墨を吐いた」という文字列を出力します。
その次にFireflySquid(ホタルイカ)クラスでSquidクラスを継承し、Swimメソッド・Spitメソッドを実行した後に作成したLuminousメソッドで「n匹のsが発光した」という文字列を出力します。
nは入力したイカの匹数でsはイカの名前です。

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

namespace UotoHotaru0402_11
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.Write("何匹のイカがいますか?:");
            // Squidクラスのインスタンス
            Squid s1 = new Squid();
            s1.Num = int.Parse(Console.ReadLine());
            s1.Name = "イカ";
            // 泳ぐ・墨を吐く
            s1.Swim();
            s1.Spit();
            // FireflySquidクラスのインスタンス
            FireflySquid s2 = new FireflySquid();
            s2.Name = "ホタルイカ";
            // 泳ぐ・墨を吐く・発光する
            s2.Swim();
            s2.Spit();
            s2.Luminous();
        }
    }
}
Squid.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_11
{
    internal class Squid
    {
        // イカの匹数
        protected static int num = 0;
        // イカの名称
        protected string name;
        // numのプロパティ
        public int Num
        {
            set { num = value; }
            get { return num; }
        }
        // nameのプロパティ
        public string Name
        {
            set { name = value; }
            get { return name;}
        }
        // 泳ぐ
        public void Swim()
        {
            Console.WriteLine("{0}匹の{1}が泳いだ", num, name);
        }
        // 墨を吐く
        public void Spit()
        {
            Console.WriteLine("{0}匹の{1}が墨を吐いた", num, name);
        }
    }
}
FireflySquid.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_11
{
    internal class FireflySquid : Squid
    {
        public void Luminous()
        {
            Console.WriteLine("{0}匹の{1}が発光した", num, name);
        }
    }
}

実行結果

実行結果からFireflySquidクラスはSquidクラスが持つフィールドnumとnameの値の取得だけでなくSwimとSqitメソッドの実行もできることがわかりました。
加えてFireflySquidクラスで作成したLuminousメソッドも実行できました。

このように基本となるクラスの性質を受け継いで独自の拡張をすることをオブジェクト指向では 継承 と呼ばれます。
継承元のSquidクラスと継承先のFireflySquidクラスの関係はベースクラス、サブクラスもしくは親クラス、子クラスと呼ばれます。
C#ではサブクラスはいくらでも存在できますがベースクラスは1つしか存在しません。
このような継承の仕方を 単一継承 といい、複数のベースクラスを継承できる場合は多重継承といいます。
こちらの技術記事によるとC++とPerlとPythonで多重継承が実装されているそうですが、使ったことがないので詳しくは存じ上げません。

CakePHPにおける継承

CakePHPでもコントローラ等で継承を用いており、例えばアカウント管理用のUsersController.phpを作成したときには以下のようにして継承します。

UsersController.php
namespace App\Controller;

use Cake\Controller\Controller;

class UsersController extends AppContoller
{
}

AppControllerがベースクラスでUsersControllerがサブクラスの関係です。
AppContollerで定義したメソッドをUsersControllerでも利用することができます。(privateでなければ)

継承を増やすと引き継いだ動作に加えて新しい動作ができるようになるので便利になると思いました。
例えば車の基本的な機能である走る、曲がる、止まるを引き継いでブレーキホールド機能や車間距離制御機能を付けることができます。

次に似た機能を同じメソッド名で実行できる機能オーバーライドについて紹介します。

C#におけるメソッドのオーバーライド

似たような動作を行うときに同じメソッド名で使い分けたい。
上記のような状況ではオーバーライド(上書き)と呼ばれるメソッドの機能が役立ちます。

オーバーライドを用いたプログラムの例を以下に書きました。
Stringクラスの勉強も兼ねて、文字列の結合にString.Concatメソッドを使うクラスをStringConcatクラス、String.Joinメソッドを使うクラスをStringJoinクラスとしています。
文字列の結合は姓と名の結合だけでなく、都道府県・市区町村・番地・建物名や年・月・日の結合にもおそらく使う、使用頻度の高いメソッドと考えられます。
文字列を結合する処理のメソッド名はFoo()メソッドとしました。

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

namespace UotoHotaru0402_10
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("3回文字列を入力してください。組み合わせた文字列が出力されます。");
            Console.Write("文字列1=");
            string s1 = Console.ReadLine();
            Console.Write("文字列2=");
            string s2 = Console.ReadLine();
            Console.Write("文字列3=");
            string s3 = Console.ReadLine();
            // StringConcatクラスのインスタンス生成
            StringConcat sc = new StringConcat();
            // StringJoinクラスのインスタンス生成
            StringJoin sj = new StringJoin();
            // それぞれのクラスのFooメソッドを実行
            sc.Foo(s1, s2, s3);
            sj.Foo(s1, s2, s3);
        }
    }
}
StringConcat.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UotoHotaru0402_10
{
    internal class StringConcat
    {
        
        public virtual void Foo(string s1, string s2, string s3)
        {
            // 文字列の結合にはString.Concatクラスを使う
            Console.WriteLine("文字列の結合(Concat):{0}", String.Concat(s1, s2, s3));
        }

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

namespace UotoHotaru0402_10
{
    internal class StringJoin : StringConcat
    {
        public virtual void Foo(string s1, string s2, string s3)
        {
            // 文字列型の配列を定義
            string[] strs = new string[] { s1, s2, s3 };
            // 文字列配列の結合にはString.Joinクラスを使う
            Console.WriteLine("文字列の結合(Join) :{0}", String.Join("/", strs));
        }
    }
}

実行結果

実行結果から入力した3つの文字列が結合された文字列が出力されました。

StringConcatクラスをベースクラス、StringJoinクラスを継承したサブクラスとしています。
普通は文字列結合のためにこのようなクラスを設定しないと思いますが、オーバーライドの動作確認とStringクラスの学習のためご了承ください。

StringConcatクラスではFoo()メソッドの引数s1、s2、s3をString.Concatメソッドの引数に指定して文字列を結合しています。
無限に結合できるわけではなく引数は4つまでです。

StringJoinクラスではまずStringConcatクラスを継承してサブクラスにします。
それからFoo()メソッドの引数s1、s2、s3をstring型の配列strsとして定義し、String.Joinメソッドの引数に区切り文字(例では"/")と配列を指定して文字列を結合しています。

StringConcatクラスのFoo()メソッドにはvirtual修飾子が付き、StringJoinクラスのFoo()メソッドにはoverride修飾子が付いています。
これにより同じFoo()メソッドを呼び出してもベースクラスの場合とサブクラスの場合では実行結果が変わります。
実行結果のようにサブクラスで名前、引数/戻り値の型がまったく同じメソッドを定義すると、ベースクラスの同名メソッドと違う動作をすることを オーバーライド と呼びます。

そしてメソッドが状況に応じて動作を使い分けるように仕組むことを ポリモーフィズム(多様性・多態性) と言います。
C#のようなポリモーフィズムに対応した言語では同名のメソッドを複数定義できます。

改めてメソッドのオーバーライドは似た機能を持つメソッドの名称統一に便利だと思いました。
機能が増えて、何をしていたメソッドだっけとなるとややこしいので。
またStringクラスは使用頻度が高いと思いますので、何度も使って引き出す時間を短くしたいです。
Stringクラスは他にも便利なメソッドがあり、こちらの技術記事にまとめられています。

最後に

5日目に学んだ内容はなかなか骨が折れる内容で1度学んだだけでは今後どう活かしていくかという先のことを見ることができませんでした。
そのため2回目、3回目と勉強して落とし込みました。

続きについては下リンクです。
https://zenn.dev/uotohotaru/articles/bfda3b63d7e156

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

Discussion