🐚

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

2022/03/29に公開

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

今回の備忘録は主なテーマは以下の3点です。

使用教材は亀田健司(著)1週間でC#の基礎が学べる本になります。

対象ページは以下の通りです。
1:298ページから310ページ
2:311ページから316ページ
3:317ページから322ページ

C#備忘録アーカイブ

C#におけるコレクション

大量のデータを扱うときには配列を使うというのは3日目の備忘録で書きました。
しかし配列には欠点が1つあります。
それはあらかじめ格納できるデータの数が決まっているということです。

実際に検証しました。
その結果、元の配列変数より大きいインデックスを入れようとするとIndexOutOfRangeExceptionクラスを返したことがわかりました。
IndexOutOfRangeExceptionクラスの公式リファレンスはこちら

検証

検証のため例えば以下のように配列変数を定義します。

// 配列を定義
Random rnd = new Random();
int[] n = new int[5];
for (int i = 0; i < 6; i++)
{
       int num = rnd.Next(1, 8);
       n[i] = num;
       Console.Write("{0}", n[i]);
}

実行結果

それではあらかじめ格納できるデータの数が決まっていないときにどのような箱を作ればよろしいでしょうか?

そのような事態に便利なのがコレクション(Collection)です。

Listクラス

コレクションはさまざまなデータ群を扱うクラス群でネームスペース「System.Collections.Generic」に含まれています。そのためコレクションのクラスを使うためにはusingディレクティブの記述が必要になります。

using System.Collections.Generic

まずはListクラス、Addメソッド、Insertメソッド、Countメソッドの例を紹介します。

  • Listクラス:長さを問わない配列のようなデータクラス
  • Addメソッド:Listクラスにデータを追加するために使うメソッド
  • Insertメソッド:指定したインデックスにデータを追加するメソッド
  • Countメソッド:Listクラスに入ったデータの総数を求めるメソッド

Listクラスは以下の書式になります

自分は実務においてjquery.add-input-areaと呼ばれるプラグインを使って、入力ボックスを増減させるフォームを作ることが度々あります。

リンク先を見ればわかりますが、Addボタンで入力ボックスを追加、Deleteボタンで入力ボックスを削除することができます。
職場ではjQueryが現役です。

さてListクラスで定義した変数に品名、個数、税込み金額という複数の要素を入れて税込み金額を求めるプログラムを組みたくなりました。
しかし理解のため簡単に教材通り1つだけの要素を入れた例を考えることにします。
例えばライブ会場の収容人数は会場によって決まっていますが、どれだけのお客さんが申し込むかは予想がつかない、キャンセルが発生するかもしれないという事例を考えます。
組んだプログラムのソースコードと実行結果を以下に示します。

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

namespace UotoHotaru0321_8
{
    internal class Program
    {
        static void Main(string[] args)
        {
	    // Listクラスを定義
            List<string> customer = new List<string>();
            int capacity = 500; //会場の収容人数
            // 値を順に挿入
            customer.Add("佐藤");
            customer.Add("藤原");
            customer.Add("吉田");
            // 2番目に"高橋"を購入
            customer.Insert(2, "高橋");
	    // Listクラスに入っているデータの総数が収容人数を超えた場合は処理を止める。
            if(customer.Count > capacity)
            {
                Console.WriteLine("収容人数を超えています。");
                return;
            }
            for (int i = 0; i < customer.Count; i++)
            {
                Console.WriteLine("来場者様[{0}]={1}", i, customer[i]);
            };
        }
    }
}

実行結果

実行結果からListクラスにデータが追加され、Inserメソッドでデータを挿入したときにインデックスが1つ後ろにずれたことがわかりました。
またListクラスを定義するときに<>を使ってリストの型を指定していますが、これは特定の型に依存しないクラスやメソッドを記述するための仕組みで ジェネリック といいます。
このジェネリックの仕組みを使ったクラスやメソッドは「ジェネリック・クラス」や「ジェネリック・メソッド」などと呼ばれます。

次にRemoveメソッドとRemoveAtメソッドを紹介します。
Removeメソッドは指定したデータ、文字列では一致する文字列のデータを削除するメソッドです。
RemoveAtメソッドは指定したインデックスのデータを削除するメソッドです。

先ほどのfor文の前に次の2行の文を入力して、実行します。

customer.Remove("佐藤");
customer.RemoveAt(0);

実行結果

実行結果からまずRemoveメソッドが実行され文字列"佐藤"と一致するデータが削除されました。
この時点でListクラス内のデータは[0 => "藤原", 1 => "高橋", 2 => "吉田"]となります。
次にRemoveAtメソッドでインデックスが0のデータが削除され、インデックスが1つ前にずれます。
最後に実行結果を表示してデータが削除できたことを確認しました。

Listクラスについて公式リファレンスでは他にもたくさんのメソッドがあります。
https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.list-1?view=net-6.0#methods

試しにfor文の前にClearメソッドを書いて実行したところ、for文の処理が実行されなくなりました。
このことからclearメソッドはListクラスのデータをすべて削除するメソッドということがわかります。

ハッシュテーブル

ハッシュテーブルとはコレクションの一種で、別名、連想記憶ともいいます。
通常の配列との違いは、配列が0, 1, 2といったインデックスの値でデータを管理しているのに対し、キー(key)と値(value)のペアを保持している点にあります。

ハッシュテーブルを実現するクラスとしてDictionaryクラスがあります。

Dictionaryクラスを使ったプログラムのソースコードを以下に示します。
書籍の名前とその作者をDictionaryクラスに入れ、最後にコンソールに表示するという内容です。

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

namespace UotoHotaru0329_2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 連想記憶クラスの生成
            Dictionary<string, string> book = new Dictionary<string, string>();
            // データの追加
            book["徒然草"] = "兼好法師";
            book["養生訓"] = "貝原益軒";
            book["自省録"] = "マルクス・アウレーリウス";
            book["論語と算盤"] = "渋沢栄一";
            foreach(String b in book.Keys)
            {
                Console.WriteLine("「{0}」の著者は{1}です。", b, book[b]);
            }

        }
    }
}

実行結果

実行結果から生成したDictionaryクラスに入れたデータを出力することができました。
本については自室で保管していた本から4つ選んでいます。
いずれも示唆に富む内容です。

Dictionaryクラスの宣言の方法は、以下になります。

Listクラスとの違いはキーと値の両方の型を宣言する必要があることです。
ハッシュテーブルは配列と違い、インデックスに順序や前後関係はございません。

キーがインデックス値ではない場合、Dictionaryクラスが必要になると思いました。

さて教材では他にはHashSetクラスが紹介されていました。
Listクラス、Dictionaryクラスでは重複したデータを入れることができるという問題があります。
重複したデータがあると、例えば同一の会社名・担当者名・連絡先を持った顧客が存在していると抽出する際に面倒になります。
上記の問題を解決し、重複のないデータを入れることができるのがHashSetクラスです。
HashSetクラスの宣言の書式は以下になります。

上記の通り、Listクラスとほぼ同様の宣言の書式になります。
そのためListクラスで書いたソースコードをHashSetにすると重複のないデータが入ります。

C#におけるデリゲート

結論から言うと同じ型の値を扱うメソッドが連続している場合にdelegateは便利と思いました。

C#におけるデリゲート(delegete)とは関数を代入できる変数のことをいいます。[1]
使うメリットとして処理を任せる側は実際の処理がどのクラスで実行され、どのような処理を行うかまったく意識しなくてもよい点にあります。

デリゲートを使ったプログラムの例を以下に示します。
目的地までの距離(m(メートル))から徒歩なら何分、自転車なら何分、自動車なら何分か計算してコンソールに表示します。
何mなら1分になるかについては徒歩=80m、自転車=250m、自動車=1000mとします。

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

namespace UotoHotaru0329_3
{
    // デリゲート
    delegate void ShowCalcResult(double m);
    
    internal class Program
    {
        // 1つ目の処理実行
        static void CalcMinutesFoot(double m)
        {
            Console.WriteLine("徒歩 :{0}分", m / 80.0);
        }
        // 2つ目の処理実行
        static void CalcMinutesBicycle(double m)
        {
            Console.WriteLine("自転車:{0}分", m / 250.0);
        }
        // 3つ目の処理の実行
        static void CaleMinutesCar(double m)
        {
            Console.WriteLine("自動車:{0}分", m / 1000.0);
        }
        static void Main(string[] args)
        {
            Console.Write("目的地までの距離は何メートル?:");
            double m = double.Parse(Console.ReadLine());
            Console.WriteLine("目的地までの距離(分)");
	    // デリゲートresultの作成
            ShowCalcResult result = new ShowCalcResult(CalcMinutesFoot);           
            // 処理の追加
            result += new ShowCalcResult(CalcMinutesBicycle);         
            result += new ShowCalcResult(CaleMinutesCar);
	    // 処理を実行して結果を出力
            result(m);
        }
    }
}

実行結果

実行結果を見ると入力した距離から目的地までの時間(分)が求まっていることがわかります。
Programクラス上で定義したデリゲートをMainメソッドで呼び出し、デリゲートresultを作成しました。
デリゲートの書式は以下の通りです。

デリゲートの特徴として同じ型の引数をとっていれば指定したメソッドにイカのように擬態し処理を実行することができます。
さらに同じ型であれば複数のメソッドを追加することができます。
変数resultにコンソールで入力した値を引数に入れるまで追加した処理が実行されることはございません。
引数に入れると処理を追加した順番、つまりCalcMinutesFoot()メソッドから実行され、CaleMinutesCar()メソッドを実行した結果を出力すると本プログラムは終了します。

C#における例外処理

エラーが発生したときの備えである例外処理について学びます。
プログラムの処理中に予期せぬ事象(エラー)が発生した時点で検知し、何らかの処理を行うことを例外処理といいます。
例外処理はtry~catchブロックで行います。
書式は以下のようになります。
:::message③
try
{
処理①
}
catch(例外クラス名 変数)
{
処理②
}
finally
{
処理③
}
:::
1個のtryに対してcatchを複数用意することが可能です。
finallyは1個だけしか定義できません。

実務ではバルクインサート処理と呼ばれる一度に数千、数万といった大量のデータを追加する必要がある場合、保存に失敗すると大量のデータが残り削除するのが面倒です。
したがって例外処理を書いて例外が発生した場合、ロールバックして保存処理前の状態に戻しています。
finallyは省略しています。

例外処理が発生する可能性のあるプログラムのソースコードを以下に示します。
入力した数値a, bから足し算する処理で文字列が入力されていた場合を考えました。

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

namespace UotoHotaru0329_4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("aとbに半角数字を入力してください");
            try
            {
                Console.Write("a:");
                int a = int.Parse(Console.ReadLine());
                Console.Write("b:");
                int b = int.Parse(Console.ReadLine());
                Console.WriteLine(Add(a, b));
            }
            catch (FormatException e)
            {
                Console.WriteLine("文字列が入力されています。");
            }
            finally
            {
                Console.WriteLine("終了");
            }
        }
        // 加算処理(数値以外が入力されていれば例外)
        private static int Add(int a, int b)
        {
            return a + b;
        }

    }
}

実行結果(例外発生時)

実行結果から半角数字以外が入力されていなかった場合、例外をキャッチしてcatch内の処理が実行されることがわかりました。
FormatExceptionはAddメソッドへの引数が数値型指定なのに文字列を渡そうとしていたため例外になりキャッチされたということです。
finallyはtry~catchが終わった後に実行されるため、実行結果で最後に「終了」と表示しています。

また throw 例外クラス と書くことで意図的に例外を発生させることも可能です。
紹介したFormatException以外にもよく使う例外クラスがこちら[2]の資料にございます。

備えあれば憂いなしということです。

以上

最後に

6日目は数に指定のないデータを扱うコレクション、相手に擬態するデリゲート、非常時の備え例外処理を学びました。
7日目はひたすら例題を解くという内容ですのでひたすらインプットです。

1周目から2周目に入って変わったことは、モチベーションが落ちてきていることとLINQやラムダ式といった教材では載っていないがC#では重要な道具の使い方を学びたいと思いました。

自分はプログラミングは動いてこそだと考えています。

そのため もしも動かなかったときに修繕する道具や、より短くまとめる道具があれば問題の解決、目標達成に近づくと考えています。

あと6日目、C#備忘録の一区切りということで書くことがあります。
各記事で文体が異なりますが、統一感は特に重視していません。
作ったプログラムの設計・動作が理解できているか、教材通りではなく自分の言葉で整理できているかを重視しています。
あとWordPressで技術記事を投稿するかもしれないので個人ブログ「まどろみみっくろん」を紹介します。

「まどろみみっくろん」は魚戸ホタルと書き手で運営しています。
魚戸ホタルは書き手のペルソナというわけではなく、あくまで副業作成の協力者、自分とは別物という感じです。
自分軸にすると生命維持以外怠けたい、必要以上働きたくない性質を持っているので、自分以外の何か、魚戸ホタル軸にしています
新しく何かを始めるにあたって、魚戸ホタル以上にちょうどいい名前が思いつかなかったので彼の名前を名乗っています。
https://madoromimicron.com/

それでは一旦C#勉強備忘録は区切りにしてC#言語を用いたフォームアプリ作成に取り組みます。

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

脚注
  1. https://tech-lab.sios.jp/archives/15318 ↩︎

  2. https://ufcpp.net/study/csharp/oo_exception.html#std ↩︎

Discussion