オブジェクト指向プログラミング(C#) -情報処理シンプルまとめ

オブジェクト指向プログラミング(C#)のブログのアイキャッチ画像 アルゴリズムとプログラミング

 ここでは,オブジェクト指向プログラミング(C#)について説明します。情報処理技術者試験とは直接関係ありませんが,実践的な内容ですので,もし,時間があれば,実際にコーディングしながら頑張ってみてください。

 実際に,C#プログラムを作成しながら進めたい場合は,「C#プログラムの作成と実行 -情報処理シンプルまとめ」を参照してください。

  1. C#プログラムの基本
    1. Consoleクラス
    2. データ構造
      1. 変数
        1. 組み込み型
        2. ユーザー定義型
        3. 定数
      2. 配列
      3. リスト
        1. 要素の追加・削除
        2. 要素の検索
        3. 要素の並べ替え
    3. 演算子
      1. 代入演算子(=)
      2. 算術演算子
      3. インクリメント演算子/デクリメント演算子
      4. 文字列の連結
    4. 基本制御構造
      1. 順次型(直線型)
      2. 分岐型(選択型)
        1. if文
          1. 等値演算子,比較演算子
          2. ブール論理演算子
        2. switch文
      3. 反復型(繰返し型)
        1. while文
        2. break文
        3. continue文
        4. do while文
        5. for文
        6. foreach文
    5. メソッド(関数)
      1. 値渡し(値呼出し)と参照渡し(参照呼出し)
      2. 可変長引数
      3. オーバーロード
  2. C#によるオブジェクト指向プログラミング
    1. クラス
      1. クラスの定義
      2. クラスの利用
    2. コンストラクタ―
    3. 静的変数,静的メソッド(関数)
      1. 静的変数,静的メソッドの宣言
      2. 静的変数,静的メソッドの利用
    4. カプセル化
      1. アクセス修飾子
      2. プロパティ
        1. 自動プロパティ
    5. 継承
      1. 派生クラスの定義
      2. 派生クラスの利用
      3. 継承の禁止
    6. ポリモーフィズム(多相性・多様性)
      1. オーバーライド
        1. 仮想メソッド
        2. 抽象クラスと抽象メソッド
        3. 仮想メソッドや抽象メソッドのオーバーライド
    7. その他
      1. インタフェース
        1. インタフェースの定義
        2. インタフェースの実装
        3. .NET Frameworkのインタフェース
      2. ジェネリック
        1. ジェネリックメソッドの定義
        2. ジェネリッククラスの定義
        3. 制約条件
      3. デリゲート(委譲)
        1. デリゲートの宣言
        2. イベント
          1. イベントの宣言
        3. Action,Func
        4. 匿名メソッド
        5. ラムダ式
  3. 非同期処理
    1. 同期処理と非同期処理
    2. asyncとawait
  4. その他
    1. Dictionary
      1. Dictionaryの宣言と定義
      2. 要素の追加・削除
      3. 要素の検索
      4. 要素の並べ替え
    2. 列挙型
      1. 列挙型の定義
      2. 列挙型の利用
      3. フラグとしての利用
    3. 構造体
      1. 構造体の定義
    4. 値型と参照型
    5. null許容型
      1. null
      2. null許容型
      3. null条件演算子(?.,?[])
      4. null合体演算子(??)
    6. 例外処理
      1. 例外のキャッチ(try,catch,finally)
      2. 例外のスロー(throw)
      3. usingステートメント
  5. 付録
    1. .NET Frameworkのインタフェース
      1. IComparable
      2. IDisposable
    2. ファイル操作
      1. テキストファイルの読み書き
      2. 任意のデータの読み書き
  6. まとめ

C#プログラムの基本

 C#プログラムの基本的な書き方は,次のようになります。

例)コンソールに「こんにちは。」と表示する

using System;

namespace csh_sample {
    class csh_sample_001 {
        static void Main(string[] args) {
            // 「こんにちは。」と表示する
            Console.WriteLine("こんにちは。");
        }
    }
}

1行目:usingディレクティブ。他の名前空間に定義されているクラスを使用する場合に記述する(ここでは,「System」名前空間のクラスにアクセスできるようにしている)

3行目:namespace(名前空間)。クラスを管理するために記述する。同じ名前のクラスを使用する場合は,この名前空間により区別する(ここでは,「csh_sample」という名前空間を定義している)

4行目:class(クラス)。オブジェクトを定義したもの(ここでは,「csh_sample_001」という名前のクラスを定義している)。詳細は「クラス」を参照

5行目:Main()メソッド(関数)。プログラムの開始地点(エントリポイント)を明示的に示すメソッド。クラス内に記述する。staticについては後述

6行目:1行コメント。プログラムの内容などを説明するために記述する(複数行のコメントを記述する場合は,/* */ を使用する)。コンパイル時には無視される

7行目:Console.WriteLine()メソッド。コンソールに文字列を表示して改行する。ConsoleクラスのメソッドでSystem名前空間に所属している

※ { }(コードブロック):名前空間やクラス,メソッドなどを定義する際に使用する

実行結果

こんにちは。

Consoleクラス

 Consoleクラスは,コンソールアプリケーションの標準入力,標準出力などを扱うためのクラスです。

※ Consoleクラスは,System名前空間に所属している

※ コンソールアプリケーション…CUI上で動作するアプリケーション

※ CUI(Character-based User Interface)…すべてのやり取りを文字だけで行うインタフェース。Windowsのコマンドプロンプトなどがある

※ 標準入力…標準的に利用するデータの入力元。通常は,キーボードが標準入力に設定されている

※ 標準出力…標準的に利用するデータの出力先。通常は,ディスプレイが標準出力に設定されている

例)入力した名前と数を表示する

using System;

namespace csh_sample {
    class csh_sample_002 {
        static void Main(string[] args) {
            int n;
            string? s;

            Console.Write("あなたの名前は:");
            s = Console.ReadLine();

            Console.Write("好きな数(整数値)は:");
            n = Convert.ToInt32(Console.ReadLine());

            Console.WriteLine("あなたの名前:{0}, 好きな数:{1}", s, n);
        }
    }
}

6行目:int型変数nの宣言。変数の詳細は後述

7行目:string型変数sの宣言。型名の後ろに?を付けることでnull許容型にすることができる。変数やnullの詳細は後述

9,12行目:Console.Write()メソッド。コンソールに文字列を表示する

10行目:Console.ReadLine()メソッド。ユーザーが入力した文字列(1行)を読み込み,変数sに代入する

13行目:Convert.ToInt32()メソッド。ReadLine()メソッドで読み取った文字列をint型に変換し,変数nに代入する。ConvertクラスのメソッドでSystem名前空間に所属している

15行目:Console.WriteLine()メソッド。コンソールに文字列を表示して改行する。{0}にsの値が,{1}にnの値が格納される

実行結果

あなたの名前は:pro
好きな数(整数値)は:7
あなたの名前:pro, 好きな数:7

データ構造

変数

 変数とは,データを一時的に格納する記憶領域のことをいいます。

※ 変数の詳細は「データ構造-変数 -情報処理シンプルまとめ」を参照

 変数は,型を明示して宣言する必要があります。

int n;    // int型変数nを宣言

 変数の宣言時に値を代入して初期化することもできます。

int n = 23;    // int型変数nを宣言し23を代入
組み込み型

 組込み型とは,C#に組み込まれた型です。

組み込み型に関する説明画像
ユーザー定義型

 ユーザー定義型は,ユーザー自身が定義できる型で,次のようなものがあります。

  • クラス
  • 構造体
  • 列挙型
  • インタフェース
  • デリゲート

など

※ 詳細は後述

定数

 定数は,変数とは違い,プログラム中で変更されない値です。C#で定数を使用する場合は,constを使用します。

const int NUM = 23;

配列

 配列とは,同じデータ型の複数のデータを連続して配置するデータ構造のことをいいます。

※ 配列の詳細は「データ構造-配列 -情報処理シンプルまとめ」を参照

 配列は,次のように宣言します。

int[] n1 = new int[3];       // 要素数3の一次元配列の宣言
int[,] n2 = new int[3, 2]    // 3行2列の二次元配列の宣言

 配列の宣言時に値を代入して初期化することもできます。

int[] n1 = new int[] {23, 33, 91};                        // 要素数3の一次元配列の宣言と値の代入
int[,] n2 = new int[,] {{11, 12}, {21, 22}, {31, 32}};    // 3行2列の二次元配列の宣言と値の代入

 各要素の値は,添字を指定して参照します。

n1[1] = 51;       // 配列n1の2番目の要素に値を代入
n2[1, 0] = 71;    // 配列n2の2行1列目の要素に値を代入

※ 配列の添字は0から始まる(例:配列n1の1番目の要素は,n[0])

リスト

 リストとは,データを連結したデータ構造のことをいいます。

※ リストは配列とは違い,要素の追加や削除を簡単に行える

※ リストの詳細は「データ構造-リスト -情報処理シンプルまとめ」を参照

 リストは,次のように宣言します。

List<string> x = new List<string>();

※ ArrayListを使用することもできる。ArrayListは,異なるデータ型の値を格納できるが,使い方が面倒だったりするので,ここではListを使用する

 リストの宣言時に値を代入して初期化することもできます。

List<string> x = new List<string>(){"おはよう", "こんにちは", "こんばんは"};

 各要素の値は,添字を指定して参照します。

string s = x[1];       // リストxの2番目の要素の値を変数sに代入

※ リストの添字は0から始まる(例:リストxの1番目の要素は,x[0])

要素の追加・削除

 リストに要素を追加する場合は,次のようにします。

x.Add("こんにちは");        // リスト末尾に要素を追加
x.Insert(2, "ばいばい");    // リストの3番目の位置に要素を追加(挿入)

 リストの要素を削除する場合は,次のようにします。

x.Remove("こんにちは");    // リストの要素を削除(削除する要素を指定)
x.RemoveAt(2);             // リストの3番目の要素を削除
x.RemoveRange(1, 3);       // リストの2番目の要素から3つの要素(4番目の要素まで)を削除
x.Clear();                 // リストの全要素を削除
要素の検索

 リストの要素を検索する場合は,次のようにします。

int n = x.IndexOf("こんにちは");       // リストの要素のインデックス番号(添字)を取得する
bool b = x.Contains("こんにちは"));    // リストの要素に検索値が含まれる場合はtrueを,含まれない場合はfalseを返す
要素の並べ替え

 リストの要素を並べ替える場合は,次のようにします。

var y = x.OrderBy(x => x);              // リストの要素を昇順で並べ替え
var y = x.OrderByDescending(x => x);    // リストの要素を降順で並べ替え

コレクション

複数のオブジェクトを扱うための仕組みで,次のようなものがある。

  • 配列
  • リスト
  • Dictionary(詳細は後述)

など

LINQ

コレクションの要素を操作するためのメソッドを集めたライブラリ(詳細は後述)。

演算子

代入演算子(=)

 代入演算子は,右辺の値を左辺に代入します。

m = n + 23;    // 変数mに変数n+23の計算結果を代入

※ 数学の「=」とは意味が違う

算術演算子

算術演算子に関する説明画像

インクリメント演算子/デクリメント演算子

インクリメント演算子/デクリメント演算子に関する説明画像

文字列の連結

 +演算子を使用して,文字列を連結することができます。

s = "あいさつ:" + "おはよう。"    // 変数sに「あいさつ:おはよう。」という文字列を代入

例)変数,定数,算術演算子の使い方

using System;

namespace csh_sample {
    class csh_sample_011 {
        static void Main(string[] args) {
            int x, y, z;
            const int NUM = 2;
            x = 3;
            y = 7;
            x += 1;
            z = (x + y) / NUM;

            Console.WriteLine("計算結果:{0}", z);
        }
    }
}

6行目:int型変数x,y,zの宣言

7行目:int型定数NUMの宣言と値の代入。定数は変数とは違い値を変更することはできない

8,9行目:変数に値を代入

10行目:変数xにx+1の計算結果(4)を代入。「x += 1」は,「x = x + 1」と同じ意味。その他,-=*=/=%=なども使用できる

11行目:変数zに(x + y) / NUMの計算結果(5)を代入。計算の順序は数学同様,x + yの計算結果をNUMで割る。また,変数zはint型なので,小数点以下の値は切り捨てられる

実行結果

計算結果:5

例)変数,定数,算術演算子の種類

using System;

namespace csh_sample {
    class csh_sample_012 {
        static void Main(string[] args) {
            byte a = 2 + 3;
            sbyte b = 2 - 3;
            long c = 2 * 3;
            int d = 7 / 3;
            float e = 7.0f / 3;         // 数値の後ろにfを付けると単精度
            double f = 7.0 / 3;
            int g = 7 % 3;
            bool h = true;
            string i = "あいさつ:";
            i += "こんにちは。";

            Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}" ,a, b, c, d, e, f, g, h, i);
        }
    }
}

実行結果

5, -1, 6, 2, 2.3333333, 2.3333333333333335, 1, True, あいさつ:こんにちは。

基本制御構造

順次型(直線型)

 順次型は,処理を順に実行する制御構造です。

※ 順次型の詳細は「アルゴリズムー基本制御構造-順次型(直線型) -情報処理シンプルまとめ」を参照

例)変数xに10を,変数yに5を,変数zにx+yの結果を代入し,zの値を表示する

using System;

namespace csh_sample {
    class csh_sample_021 {
        static void Main(string[] args) {
            int x, y, z;
            x = 10;
            y = 5;
            z = x + y;
            
            Console.WriteLine(z);
        }
    }
}

6行目:変数の宣言

7~9行目:変数に値を代入

実行結果

15

分岐型(選択型)

 分岐型は,条件に従い処理の流れを分ける制御構造です。

※ 分岐型の詳細は「アルゴリズムー基本制御構造-分岐型(選択型) -情報処理シンプルまとめ」を参照

if文

 if文は,次のように記述します。

if (条件式) {
    処理;         // 条件式がtrueの場合に実行される
}
if (条件式) {
    処理1;        // 条件式がtrueの場合に実行される
} else {
    処理2;        // 条件式がfalseの場合に実行される
}
if (条件式1) {
    処理1;        // 条件式1がtrueの場合に実行される
} else if (条件式2) {
    処理2;        // 条件式1がfalseで条件式2がtrueの場合に実行される
} else {
    処理z;        // 条件式1,2がfalseの場合に実行される
}

※ else ifは複数記述することができる

等値演算子,比較演算子
等値演算子,比較演算子に関する説明画像
ブール論理演算子
ブール論理演算子に関する説明画像

例)変数xに数値を入力して, その値が80以上ならば変数rに「合格」を,それ以外ならば「不合格」を代入して表示する

using System;

namespace csh_sample {
    class csh_sample_022 {
        static void Main(string[] args) {
            int x;
            string r;

            Console.Write("xを入力:");
            x = Convert.ToInt32(Console.ReadLine());

            if(x >= 80) {
                r = "合格";
            } else {
                r = "不合格";
            }

            Console.WriteLine(r);
        }
    }
}

12~16行目:合否の判定。変数xが80以上の場合は13行目の処理を行い,それ以外の場合は15行目の処理を行う

実行結果

xを入力:80
合格

例)変数xに数値を入力して, その値が80以上ならば変数rに「A」を,80以上ではなく(80未満で)60以上ならば「B」を,それ以外ならば「C」を代入して表示する

using System;

namespace csh_sample {
    class csh_sample_023 {
        static void Main(string[] args) {
            int x;
            string r;

            Console.Write("xを入力:");
            x = Convert.ToInt32(Console.ReadLine());

            if(x >= 80) {
                r = "A";
            } else if(x >= 60) {
                r = "B";
            } else {
                r = "C";
            }

            Console.WriteLine(r);
        }
    }
}

12~18行目:A~Cの判定。変数xが80以上の場合は13行目の処理を行い,80以上ではなく(80未満で)60以上の場合は15行目の処理を行い,それ以外の場合は17行目の処理を行う

実行結果

xを入力:75
B

例)変数x1とx2に数値を入力して, その値がともに80以上ならば変数rに「合格」を,それ以外ならば「不合格」を代入して表示する

using System;

namespace csh_sample {
    class csh_sample_024 {
        static void Main(string[] args) {
            int x1, x2;
            string r;

            Console.Write("x1を入力:");
            x1 = Convert.ToInt32(Console.ReadLine());
            Console.Write("x2を入力:");
            x2 = Convert.ToInt32(Console.ReadLine());

            if((x1 >= 80) && (x2 >= 80)) {
                r = "合格";
            } else {
                r = "不合格";
            }

            Console.WriteLine(r);
        }
    }
}

14~18行目:合否の判定。変数x1とx2がともに80以上の場合は15行目の処理を行い,それ以外の場合は17行目の処理を行う

実行結果

x1を入力:85
x2を入力:80
合格
switch文

 switch文は,次のように記述します。

switch (変数) {
    case 値1:
        処理1;    // 変数の値が値1に等しい場合に実行される
        break;
    case 値2:
        処理2;    // 変数の値が値2に等しい場合に実行される
        break;
    default:
        処理z;    // 変数の値が上のどの値とも等しくない場合に実行される
        break;
}
switch (変数) {
    case int n when n >= 値1:
        処理1;    // 変数の値が値1以上の場合に実行される
        break;
    case int n when n >= 値2:
        処理2;    // 変数の値が値1以上ではなく(値1未満で)値2以上の場合に実行される
        break;
    default:
        処理z;    // その他の場合に実行される
        break;
}

例)変数sに文字を入力して, その文字が g ならば変数rに「グー」を,t ならば「チョキ」を,それ以外ならば「パー」を代入して表示する

using System;

namespace csh_sample {
    class csh_sample_025 {
        static void Main(string[] args) {
            string? s;
            string r;

            Console.Write("sを入力:");
            s = Console.ReadLine();

            switch (s) {
                case "g":
                    r = "グー";
                    break;
                case "t":
                    r = "チョキ";
                    break;
                default:
                    r = "パー";
                    break;
            }

            Console.WriteLine(r);
        }
    }
}

12~22行目:ジャンケンの判定。変数sが「g」の場合は14行目の処理を,「t」の場合は17行目の処理を,それ以外の場合は20行目の処理を行う

実行結果

sを入力:g
グー

例)変数xに数値を入力して, その値が80以上ならば変数rに「A」を,80以上ではなく(80未満で)60以上ならば「B」を,それ以外ならば「C」を代入して表示する

using System;

namespace csh_sample {
    class csh_sample_026 {
        static void Main(string[] args) {
            int x;
            string r;

            Console.Write("xを入力:");
            x = Convert.ToInt32(Console.ReadLine());

            switch(x) {
                case int n when n >= 80:
                    r = "A";
                    break;
                case int n when n >= 60:
                    r = "B";
                    break;
                default:
                    r = "C";
                    break;
            }

            Console.WriteLine(r);
        }
    }
}

12~22行目:A~Cの判定。変数xが80以上の場合は14行目の処理を行い,80以上ではなく(80未満で)60以上の場合は17行目の処理を行い,それ以外の場合は20行目の処理を行う

実行結果

xを入力:75
B

反復型(繰返し型)

 反復型は,繰り返し条件を満たしている間,処理を繰り返す制御構造です。

※ 反復処理…繰返し同じ処理を実行すること。ループ処理ともいう

※ 反復型の詳細は「アルゴリズムー基本制御構造-反復型(繰返し型) -情報処理シンプルまとめ」を参照

while文

 while文は,次のように記述します。

while (条件式) {
    処理;         // 条件式がtrueの間,繰り返し実行される
}

例)変数xに数値を入力(x≧1とする)して,その値の回数だけ「OK」と表示する

using System;

namespace csh_sample {
    class csh_sample_027 {
        static void Main(string[] args) {
            int x;

            Console.Write("xを入力:");
            x = Convert.ToInt32(Console.ReadLine());

            while(x >= 1) {
                Console.Write("OK");
                x -= 1;
            }
        }
    }
}

11~14行目:変数xが1以上の間,12~13行目の処理を繰り返す(12行目で「OK」と表示し,13行目で変数xの値を1減らす)

実行結果

xを入力:3
OKOKOK
break文

 break文は,ループなどを抜ける場合に使用します。

while (条件式1) {
    …
    if(条件式2) {
        break;    // 条件式2がtrueの場合,ループを抜ける(これより後ろの処理は実行されない)
    }
    …
}
…    // ループを抜けると,この行に処理が移る
continue文

 continue文は,ループの先頭に戻る場合に使用します。

while (条件式1) {
    …
    if(条件式2) {
        continue;    // 条件式2がtrueの場合,ループの先頭に戻る(これより後ろの処理は実行されない)
    }
    …
}

※ continue文が実行されると先頭に戻って条件式1の判定を行う(条件式1がtrueの場合は処理を繰り返す)

do while文

 do while文は,次のように記述します。

do {
    処理;         // 条件式がtrueの間,繰り返し実行される(少なくとも1回は実行される)
} while (条件式);

例)変数xに数値を入力(x≧1とする)して,1からxまでの値を加えたものを変数rに代入し,その結果を表示する

using System;

namespace csh_sample {
    class csh_sample_028 {
        static void Main(string[] args) {
            int x, r;

            Console.Write("xを入力:");
            x = Convert.ToInt32(Console.ReadLine());

            r = 0;
            do {
                r += x;
                x -= 1;
            } while(x >= 1);

            Console.Write(r);
        }
    }
}

12~15行目:変数xが1以上の間,13~14行目の処理を繰り返す(13行目で加算を行い,14行目で変数xの値を1減らす)

実行結果

xを入力:4
10
for文

 for文は,次のように記述します。

for (初期化; 条件式; 更新) {
    処理;         // 条件式がtrueの間,繰り返し実行される
}

例)配列xに文字列を代入し,順に表示する

using System;

namespace csh_sample {
    class csh_sample_029 {
        static void Main(string[] args) {
            string[] x = new string[] {"おはよう", "こんにちは", "こんばんは"};

            for(int i = 0; i < x.Length; i++) {
                Console.WriteLine(x[i]);
            }
        }
    }
}

8~10行目:(ループ)変数iが0からx.Length(配列の要素数=3)未満までの間,9行目の処理を繰り返す(配列xの各要素の値を順に表示する)

実行結果

おはよう
こんにちは
こんばんは

例)二次元配列の各要素の値を表示する

using System;

namespace csh_sample {
    class csh_sample_030 {
        static void Main(string[] args) {
            int[,] k = new int[,] {{11, 12, 13, 14}, {21, 22, 23, 24}, {31, 32, 33, 34}};
            int row = k.GetLength(0);
            int col = k.GetLength(1);

            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    if(j != col - 1) {
                        Console.Write(k[i, j] + ", ");
                    } else {
                        Console.WriteLine(k[i, j]);
                    }
                }
            }
        }
    }
}

10~18行目:(ループ)変数iが0からrow(行数=3)未満までの間,11~17行目の処理を繰り返す(二重ループの外側のループ)

11~17行目:(ループ)変数jが0からcol(列数=4)未満までの間,12~16行目の処理を繰り返す(二重ループの内側のループ)

12~16行目:行の終わりでない場合は二次元配列のi列j番目の要素の値と「, 」を表示(表示後,改行はしない)し,それ以外の場合(行の終わりの場合)はi列j番目の要素の値を表示して改行する

実行結果

11, 12, 13, 14
21, 22, 23, 24
31, 32, 33, 34
foreach文

 foreach文は,次のように記述します。

※ foreach文は配列のすべての要素にアクセスする場合に使用すると便利

foreach (変数宣言 in 配列) {
    処理;         // 配列の各要素に対する処理が実行される
}

※ 変数宣言する際の型は,配列の型と同じにする

例)配列xに文字列を代入し,順に表示する

using System;

namespace csh_sample {
    class csh_sample_031 {
        static void Main(string[] args) {
            string[] x = new string[] {"おはよう", "こんにちは", "こんばんは"};

            foreach(string s in x) {
                Console.WriteLine(s);
            }
        }
    }
}

8~10行目:配列xの要素を先頭から順に最後まで取り出し,9行目の処理を繰り返し行う(配列xの各要素の値を順に表示する)

実行結果

おはよう
こんにちは
こんばんは

例)リストnum1に要素を追加・削除し,値の昇順に並べ替えて表示する

using System;

namespace csh_sample {
    class csh_sample_032 {
        static void Main(string[] args) {
            List<int> num1 = new List<int>();
            num1.Add(33);
            num1.Add(23);
            num1.Add(91);
            num1.Insert(2, 700);
            num1.RemoveAt(2);
            
            var num2 = num1.OrderBy(x => x);

            foreach (int n in num2) {
                Console.WriteLine(n);
            }
        }
    }
}

7~9行目:リストnum1に要素を追加

10行目:リストnum1の3番目の位置に要素を追加(挿入)

11行目:リストnum1の3番目の要素を削除

13行目:リストnum1の要素を昇順で並べ替え,(IOrderedEnumerable<int>型)変数num2に代入

15~17行目:リストnum2の要素を先頭から順に最後まで取り出し,16行目の処理を繰り返し行う(リストnum2の各要素の値を順に表示する)

実行結果

23
33
91

メソッド(関数)

 メソッドとは,複数のプログラムに共通する処理を別のプログラムとして独立させたもの(副プログラム)をいいます。

※ 副プログラムの詳細は「流れ図と擬似言語によるプログラム-副プログラム(サブルーチン) -情報処理シンプルまとめ」を参照

 メソッドは,次のように宣言,定義します。

戻り値の型 メソッド名(引数1,[引数2],…) {
    処理;
}

※ 戻り値がない場合,戻り値の型は「void」となる(戻り値があるものを関数,ないものを手続きと言うこともあるが,C#では,戻り値がなくてもメソッドという)

※ 引数は複数あってもいいし,なくてもよい

例)メソッドの使用方法

using System;

namespace csh_sample {
    class csh_sample_051 {
        static void Main(string[] args) {
            string name = "Aくん";
            int hp = 100;

            ViewData(name, hp);
            hp = IncreaseHP(hp, 10);
            ViewData(name, hp);
        }

        // 戻り値のないメソッド
        static void ViewData(string _name, int _hp) {
            Console.WriteLine("{0}, HP{1}", _name, _hp);
        }

        // 戻り値のあるメソッド
        static int IncreaseHP(int _hp, int _value ) {
            return _hp += _value;
        }
    }
}

9~11行目:メソッドの呼び出し。戻り値のあるメソッドを呼び出した場合は,戻り値を変数に格納できる

※ メソッド呼び出し時に渡す引数を実引数という

15~17行目:戻り値のないメソッドの宣言と定義。戻り値のないメソッドの場合,returnは必要ないが,記述する場合は「return;」とし,returnの後ろには何も書かない

20~22行目:戻り値のあるメソッドの宣言と定義。戻り値は,returnの後ろに書く

※ メソッド定義時の値を受け取る引数を仮引数という

実行結果

Aくん, HP100
Aくん, HP110

値渡し(値呼出し)と参照渡し(参照呼出し)

 C#では,値の受け渡し方に値渡しと参照渡しがあります。 

※ 値渡しと参照渡しの詳細は「流れ図と擬似言語によるプログラム-値渡し(値呼出し)と参照渡し(参照呼出し) -情報処理シンプルまとめ」を参照

 参照渡しをする場合は,次の修飾子を使用します。

※ 値渡しの場合,修飾子は必要ない

値渡し(値呼出し)と参照渡し(参照呼出し)に関する説明画像

例)値渡しと参照渡し

using System;

namespace csh_sample {
    class csh_sample_052 {
        static void Main(string[] args) {
            int m1 = 2;
            int m2 = 3;
            int m3;

            // Console.WriteLine("メソッド実行前:m1={0}, m2={1} m3={2}", m1, m2, m3);    // エラー
            Console.WriteLine("メソッド実行前:m1={0}, m2={1}, m3は未割り当て", m1, m2);
            Calc(m1, ref m2, out m3);
            Console.WriteLine("メソッド実行後:m1={0}, m2={1}, m3={2}", m1, m2, m3);
        }

        static void Calc(int a1, ref int a2, out int a3) {
            a1 = a1 + 1;
            a2 = a2 + 1;
            a3 = 9;
            Console.WriteLine("メソッド実行時:a1={0}, a2={1}, a3={2}", a1, a2, a3);
        }
    }
}

6~8行目:変数の宣言と値の代入

11行目:Calc()メソッド実行前の各変数の値を表示(変数m3には値を代入していないので使用するとエラーになる(10行目))

12行目:Calc()メソッドの呼び出し。変数m1(値渡し)と変数m2(参照渡し:ref)は初期化していなければならないが,変数m3(参照渡し:out)は初期化していなくてもよい

13行目:Calc()メソッド実行後の各変数の値を表示(変数m1(値渡し)の値は変わっていないが,変数m2(参照渡し:ref)の値は変わっている。変数m3(参照渡し:out)には値が割り当てられている

16~21行目:Calc()メソッドの宣言と定義。引数a1は値渡し,引数a2は参照渡し(ref),引数a3は参照渡し(out)

17~19行目:各変数に値を代入。引数a1(値渡し),a2(参照渡し:ref)には別の値を割り当てなくてもよいが,引数a3(参照渡し:out)には値を割り当てなければならない

実行結果

メソッド実行前:m1=2, m2=3, m3は未割り当て
メソッド実行時:a1=3, a2=4, a3=9
メソッド実行後:m1=2, m2=4, m3=9

可変長引数

 メソッドに可変長引数を渡す場合は,paramsを使用します。

例)可変長引数

using System;

namespace csh_sample {
    class csh_sample_053 {
        static void Main(string[] args) {
            int[] m = new int[] {7, 3, 5, 4};
            int count, result;

            result = Calc(out count, m);
            Console.WriteLine("{0}:{1}つの合計:{2}", String.Join(",", m), count, result);
            result = Calc(out count, 2, 0, 8);
            Console.WriteLine("{0}つの合計:{1}", count, result);
        }

        static int Calc(out int _count, params int[] _num) {
            int r = 0;
            _count = 0;

            foreach(int n in _num) {
                r += n;
                _count++;
            }

            return r;
        }
    }
}

9,11行目:Calc()メソッドの呼び出し。可変長引数の部分には,配列(9行目)や可変個の引数(11行目)を渡すことができる

15~25行目:Calc()メソッドの宣言と定義。引数_numには可変個の引数を渡すことができる(可変長引数は,引数リストの最後に配列として宣言する)

実行結果

7,3,5,4:4つの合計:19
3つの合計:10

オーバーロード

 メソッドは,名前が同じで引数が異なるものを複数作ることができます(オーバーロード)。

※ 引数の名前だけが違う場合や,戻り値だけが違う場合,refやoutが違うだけの場合,オーバーロードは作れない

※ オーバーロードの詳細は「オブジェクト指向設計-オブジェクト指向のその他の概念-オーバーロード -情報処理シンプルまとめ」を参照

例)メソッドのオーバーロード

using System;

namespace csh_sample {
    class csh_sample_054 {
        static void Main(string[] args) {
            Calc("こんにちは。");
            Calc(7);
            Calc(2.1, 3.5);
        }

        static void Calc(string _str) {
            Console.WriteLine("{0}", _str);
        }

        static void Calc(int _n) {
            Console.WriteLine("半径{0}の円の面積は{1}", _n, _n * _n * 3.14);
        }

        static void Calc(double _n1, double _n2) {
            Console.WriteLine("{0}+{1}={2}", _n1, _n2, _n1 + _n2);
        }
    }
}

6~8行目:メソッドの呼び出し。6行目では文字列を受け取るCalc()メソッドが,7行目では整数値を受け取るCalc()メソッドが,8行目では実数値を2つ受け取るCalc()メソッドが呼び出される

11~21行目:Calc()メソッドの宣言と定義。引数が異なる同じ名前のメソッドを宣言し定義している

実行結果

こんにちは。
半径7の円の面積は153.86
2.1+3.5=5.6

C#によるオブジェクト指向プログラミング

クラス

クラスの定義

 クラスは,次のように定義します。

アクセス修飾子 class クラス名 {
    メンバ変数やメソッド(メンバ関数)の定義
}

※ メンバ変数…属性(データ)

※ メンバ関数…メソッド(手続き)

※ アクセス修飾子については後述

クラスの利用

 クラスを利用する場合は,インスタンスを生成します。

※ インスタンス…クラスを元に生成されたオブジェクト

変数 = new クラス名(…);

※ newを使用してインスタンスを生成する

※ コンストラクタ―…インスタンス生成時に呼び出されるメソッド(「クラス名(…);」の部分)。詳細は後述

 インスタンスを生成したら,メンバ変数やメソッドを利用できるようになります。

変数.メンバ変数;
変数.メソッド(…);

例)クラスの定義と利用

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    class GCharacter {
        public string? name;
        public int hp;

        public void ViewData() {
            Console.WriteLine("{0}, HP:{1}", name, hp);
        }
    }
}

※ public(アクセス修飾子)については後述

5~12行目:GCharacterクラスの定義

6,7行目:メンバ変数の宣言。型名の後ろに?を付けることでnull許容型にすることができる(null許容型については後述)

9~11行目:メソッドの宣言と定義

csh_sample_101.cs

using System;

namespace csh_sample {
    class csh_sample_101 {
        static void Main(string[] args) {
            GCharacter gChar = new GCharacter(); 

            gChar.name = "Aくん";
            gChar.hp = 100;

            gChar.ViewData();
        }
    }
}

6行目:GCharacterクラスのインスタンスを生成し,変数gCharに代入

8~9行目:メンバ変数に値を代入

11行目:メソッドの呼び出し

実行結果

Aくん, HP:100

コンストラクタ―

 コンストラクタ―は,インスタンス生成時に呼び出されるメソッドです。

※ コンストラクタ―は,クラス名と同じ名前でなければならず,戻り値の型も書かない

例)コンストラクタ―の定義と利用

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    class GCharacter {
        public string? name;
        public int hp;

        // コンストラクタ―
        public GCharacter() {}

        // コンストラクタ―
        public GCharacter(string _name, int _hp) {
            name = _name;
            hp = _hp;
        }

        public void ViewData() {
            Console.WriteLine("{0}, HP:{1}", name, hp);
        }
    }
}

※ public(アクセス修飾子)については後述

10行目:コンストラクタ―の宣言

13~16行目:コンストラクタ―(引数あり)の宣言と定義(メンバ変数を初期化する)

csh_sample_102.cs

using System;

namespace csh_sample {
    class csh_sample_102 {
        static void Main(string[] args) {
            GCharacter gChar1 = new GCharacter(); 
            GCharacter gChar2 = new GCharacter("Aくん", 100); 

            gChar1.ViewData();
            gChar2.ViewData();
        }
    }
}

6行目:GCharacterクラスのインスタンスを生成し,変数gChar1に代入(引数なしのコンストラクタ―が呼び出される)

7行目:GCharacterクラスのインスタンスを生成し,変数gChar2に代入(引数ありのコンストラクタ―が呼び出され,メンバ変数が初期化される)

9~10行目:メソッドの呼び出し

実行結果

, HP:0
Aくん, HP:100

静的変数,静的メソッド(関数)

 静的変数,静的メソッドは,クラスに属するメンバ変数,メソッドで,その値や処理は,すべてのインスタンスで共有されます。

※ 静的クラス…静的変数や静的メソッドなどしか定義できない(インスタンス化できない)クラス

静的変数,静的メソッドの宣言

 静的変数,静的メソッドは,次のように定義します。

static 型名 変数名;
static 戻り値の型 メソッド名(引数1, [引数2], …) {
    処理;
}

※ 静的変数や静的メソッドを宣言する際には,staticを付ける

静的メソッドは,静的変数などにしかアクセスできない

静的変数,静的メソッドの利用

 静的変数,静的メソッドは,次のように利用します。

クラス名.メンバ変数;
クラス名.メソッド(…);

※ インスタンスは生成しない

例)静的変数や静的メソッドの定義と利用

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    class GCharacter {
        public string name;
        public static string classname;

        // コンストラクタ―
        public GCharacter(string _name) {
            name = _name;
        }

        // 静的コンストラクタ―
        static GCharacter() {
            classname = "GCharacter";
        }

        // メソッド
        public void ViewData() {
            Console.WriteLine("クラス名:{0} 名前:{1}", classname, name);
        }

        // 静的メソッド
        public static void ViewClassInfo() {
            // Console.WriteLine("クラス名:{0} 名前:{1}", classname, name);    // エラー
            Console.WriteLine("クラス名:{0}", classname);
        }
    }
}

※ public(アクセス修飾子)については後述

7行目:静的メンバ変数の宣言

15~17行目:静的コンストラクタ―の宣言と定義(静的メンバ変数を初期化する)

※ 静的コンストラクタ―は,クラスにアクセスした際に1度だけ呼び出される

20~22行目:メソッドの宣言と定義(静的変数を使用することができる(21行目))

25~28行目:静的メソッドの宣言と定義(静的変数しか使用できない(27行目))

csh_sample_103.cs

using System;

namespace csh_sample {
    class csh_sample_103 {
        static void Main(string[] args) {
            GCharacter gChar = new GCharacter("Aくん"); 

            gChar.ViewData();
            GCharacter.ViewClassInfo();
        }
    }
}

9行目:静的メソッドの呼び出し(インスタンス化しない)

実行結果

クラス名:GCharacter 名前:Aくん
クラス名:GCharacter

カプセル化

 外部(クラス外)に公開する必要のないメンバ変数やメソッドについては,外部から直接アクセスできないようにするのが良いとされています(クラス内を隠ぺいします)。

※ 外部からメンバ変数にアクセスする場合はプロパティを使用する(プロパティの詳細は後述)

アクセス修飾子

 アクセス修飾子とは,外部からのアクセスに制限をかけるもので,クラスやメンバ変数,メソッドに指定できます。

アクセス修飾子の種類に関する説明画像

プロパティ

 プロパティは,メンバ変数の値を取得したり変更したりするメソッド(アクセサー)です。

※ プロパティは,クラス内からはメソッドのように見え,クラス外からはメンバ変数のように見える

アクセス修飾子 型名 プロパティ名 {
    get {
        値の取得時の処理;
        return 取得した値;
    }
    set {
        値の変更時の処理(valueという変数に値が代入されているので,それを使用する);
    }
}

※ getアクセサーだけのプロパティを定義することもできる

自動プロパティ

 自動プロパティは,get/setアクセサーを省略する場合に使用できます。

アクセス修飾子 型名 プロパティ名 { get; set; }

※ メンバ変数は自動的に生成されるが参照できない

例)メンバ変数の隠ぺいとプロパティの使用方法

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    class GCharacter {
        private int hp;

        public GCharacter(string _name, int _hp) {
            Name = _name;
            Hp = _hp;
        }
        
        // Nameプロパティ
        public string Name { get; set; }

        // Hpプロパティ
        public int Hp {
            get { return this.hp; }
            protected set {
                if(value >= 999) { value = 999; }
                else if(value < 0) { value = 0; }
                 this.hp = value;
            }
        }
    }
}

6行目:メンバ変数の宣言。外部から隠ぺい(private)

8~11行目:コンストラクタ―の宣言と定義(ここでは,プロパティに値を代入しているが,コンストラクターからであれば,隠蔽しているメンバ変数に値を代入することもできる)

9,10行目:NameプロパティとHpプロパティのsetアクセサーが呼び出される

※ Hpプロパティのsetアクセサーには,クラス内と派生クラスからのみアクセス可能(protected)

14行目,17~24行目:プロパティの定義

csh_sample_131.cs

using System;

namespace csh_sample {
    class csh_sample_131 {
        static void Main(string[] args) {
            GCharacter gChar = new GCharacter("Aくん", -10); 

            gChar.Name = "Bくん";
            // gChar.Hp = 8000;         // エラーになる

            Console.WriteLine("{0}, HP:{1}", gChar.Name, gChar.Hp);
        }
    }
}

6行目:GCharacterクラスのインスタンスを生成し,変数gCharに代入

8行目:Nameプロパティのsetアクセサーが呼び出される

9行目:Hpプロパティのsetアクセサーは呼び出せない(外部からアクセスできない(protected)ため)

11行目:Nameプロパティのgetアクセサーと,Hpプロパティのgetアクセサーが呼び出される

実行結果

Bくん, HP:0

継承

 継承とは,ある上位クラス(基底クラス,スーパークラス)のメンバ変数やメソッドを,下位クラス(派生クラス,サブクラス)に引き継ぐ性質をいいます。サブクラスを定義する場合,継承により基底クラスのメンバ変数やメソッドに対する差異(差分)だけを定義すればよいので開発効率が高まります。

※ クラスは,階層構造を持つことができる(あるクラスを基本として,別の新しいクラスを作ることができる)

※ 多重継承…複数の上位クラスからメンバ変数やメソッドを継承すること。C++では可能。C#やJavaなどではできない

派生クラスの定義

 派生クラスは,次のように定義します。

アクセス修飾子 class 派生クラス名 : 基底クラス名 {
    派生クラスのメンバ変数やメソッド(メンバ関数)の定義
}

派生クラスの利用

 派生クラスを利用する場合も,インスタンスを生成します。

変数 = new 派生クラス名(…);

 インスタンスを生成したら,メンバ変数やメソッドを利用できるようになります。

変数.メンバ変数;
変数.メソッド(…);

基底クラスのメンバ変数や,メソッドも利用できる

例)派生クラスの宣言,定義と利用

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    class GCharacter {
        public string? Name{ get; set; }
        public int Hp{ get; set; }

        public GCharacter() {
            Console.WriteLine("基底(GCharacter)クラスのコンストラクター");
        }
        
        public GCharacter(string _name, int _hp) {
            Name = _name;
            Hp = _hp;
        }
    }
}

6,7行目:プロパティの宣言と定義

9~11行目:コンストラクタ―(引数なし)の宣言と定義

13~16行目:コンストラクタ―(引数あり)の宣言と定義

GEnemy.cs

using System;

namespace csh_sample {
    // GEnemyクラスの定義
    class GEnemy : GCharacter {
        public string? Position { get; set; }

        public GEnemy(){
            Console.WriteLine("派生(GEnemy)クラスのコンストラクター");
        }

        public GEnemy(string _name, int _hp, string _position) : base(_name, _hp){
            Position = _position;
        }
    }
}

5~15行目:GCharacterクラスを継承したGEnemyクラスの定義

6行目:プロパティの宣言と定義

8~10行目:コンストラクタ―の宣言と定義。インスタンス生成時に,基底クラスのコンストラクターも呼び出される(基底クラスのコンストラクターを明示していない場合は,引数なしのコンストラクターが呼び出される)

12~14行目:コンストラクタ―の宣言と定義。インスタンス生成時に,基底クラスのコンストラクターも呼び出される(引数ありのコンストラクターを呼び出す場合は明示する)

※ 基底クラスのコンストラクター ⇒ 派生クラスのコンストラクター の順に呼び出される(実行結果1~2行目を参照)

GFriend.cs

using System;

namespace csh_sample {
    // GFriendクラスの定義
    class GFriend : GCharacter {
        public string? Work { get; set; }

        public GFriend() {
            Console.WriteLine("派生(GFriend)クラスのコンストラクター");
        }
        
        public GFriend(string _name, int _hp, string _work) : base(_name, _hp){
            Work = _work;
        }
    }
}

5~15行目:GCharacterクラスを継承したGFriendクラスの定義

6行目:プロパティの宣言と定義

8~10行目:コンストラクタ―の宣言と定義。インスタンス生成時に,基底クラスのコンストラクターも呼び出される(基底クラスのコンストラクターを明示していない場合は,引数なしのコンストラクターが呼び出される)

12~14行目:コンストラクタ―の宣言と定義。インスタンス生成時に,基底クラスのコンストラクターも呼び出される(引数ありのコンストラクターを呼び出す場合は明示する)

※ 基底クラスのコンストラクター ⇒ 派生クラスのコンストラクター の順に呼び出される(実行結果3~4行目を参照)

csh_sample_132.cs

using System;

namespace csh_sample {
    class csh_sample_132 {
        static void Main(string[] args) {
            GFriend gFrd1 = new GFriend();
            GFriend gFrd2 = new GFriend("Aくん", 100, "勇者");
            GEnemy gEmy1 = new GEnemy();
            GEnemy gEmy2 = new GEnemy("ボスB", 550, "ボス");
            GCharacter gChar = new GCharacter("Cくん", 80);

            Console.WriteLine("{0}, HP:{1}, Work:{2}", gFrd2.Name, gFrd2.Hp, gFrd2.Work);
            Console.WriteLine("{0}, HP:{1}, Position:{2}", gEmy2.Name, gEmy2.Hp, gEmy2.Position);
            Console.WriteLine("{0}, HP:{1}", gChar.Name, gChar.Hp);
        }
    }
}

6行目:GFriendクラスのインスタンスを生成し,変数gFrd1に代入(基底クラスの引数なしのコンストラクターも呼び出される)

7行目:GFriendクラスのインスタンスを生成し,変数gFrd2に代入(基底クラスの引数ありのコンストラクターも呼び出される)

8行目:GEnemyクラスのインスタンスを生成し,変数gEmy1に代入(基底クラスの引数なしのコンストラクターも呼び出される)

9行目:GEnemyクラスのインスタンスを生成し,変数gEmy2に代入(基底クラスの引数ありのコンストラクターも呼び出される)

10行目:GCharacterクラスのインスタンスを生成し,変数gCharに代入

12行目:変数gFrd2の各プロパティの値を表示する(基底クラスのプロパティも,そのまま利用できる)

13行目:変数gEmy2の各プロパティの値を表示する(基底クラスのプロパティも,そのまま利用できる)

14行目:変数gCharの各プロパティの値を表示する(基底クラスのプロパティしか利用できない)

実行結果

基底(GCharacter)クラスのコンストラクター
派生(GFriend)クラスのコンストラクター
基底(GCharacter)クラスのコンストラクター
派生(GEnemy)クラスのコンストラクター
Aくん, HP:100, Work:勇者
ボスB, HP:550, Position:ボス
Cくん, HP:80

継承の禁止

 継承させたくないクラスについては,クラスの継承を禁止することができます。

sealed class クラス名 {
    メンバ変数やメソッド(メンバ関数)の定義
}

※ クラスを定義する際に,sealedを付けると継承を禁止することができる

ポリモーフィズム(多相性・多様性)

 ポリモーフィズムとは,複数の子クラスに対して同じメッセージを送った場合に,それぞれのクラスが異なる動作をするという特性をいいます。

オーバーライド

 オーバーライドとは,上位クラスで定義されたメソッドを,サブクラスで再定義することをいいます。

※ ポリモーフィズムを実現するためには,上位クラスから引き継いだメソッドを,それぞれのサブクラスに合った内容で再定義し,動作を上書きする

仮想メソッド

 仮想メソッドは,サブクラスで再定義できるメソッドです。基底クラスで,次のように定義します。

public virtual 戻り値の型 メソッド名(引数1,[引数2],…) {
    処理;
}

※ 仮想メソッドを定義する際にvirtualを付ける

抽象クラスと抽象メソッド

 抽象クラスとは,抽象メソッドを含むクラスのことをいいます。インスタンスを生成することはできません。

抽象クラスは,継承することを前提としたクラスである

 抽象メソッドとは,実装を持たない宣言のみをしたメソッドのことをいいます。抽象メソッドは,次のように宣言します。

※ 抽象メソッドは,抽象クラス内でのみ宣言できる

※ 抽象メソッドの実装は派生クラスで行う(行わなければならない)

abstract class 抽象クラス名 {
    ・・・
    public abstract 戻り値の型 メソッド名(引数1,[引数2],…) { }
    ・・・
}

※ 抽象クラスや抽象メソッドを宣言する際にabstractを付ける

仮想メソッドや抽象メソッドのオーバーライド

 仮想メソッドや抽象メソッドのオーバライドは,次のようにします。

public override 戻り値の型 メソッド名(引数1,[引数2],…) {
    処理;
}

※ メソッドを定義する際にoverrideを付ける

例)抽象クラスと,仮想メソッド,抽象メソッドのオーバーライド

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    abstract class GCharacter {
        public string Name { get; set; }

        public GCharacter(string _name) {
            Name = _name;
        }

        // 仮想メソッド
        public virtual void ViewDataV() {
            Console.Write("名前:{0}", Name);
        }

        // 抽象メソッド
        public abstract void ViewDataA();
    }
}

5~19行目:抽象クラスの定義

13~15行目:仮想メソッドの宣言と定義

18行目:抽象メソッドのの宣言(実装は持たない)

GEnemy.cs

using System;

namespace csh_sample {
    // GEnemyクラスの定義
    class GEnemy : GCharacter {
        public string Position { get; set; }

        public GEnemy(string _name, string _position) : base(_name){
            Position = _position;
        }

        // 仮想メソッドのオーバーライド
        public override void ViewDataV() {
            base.ViewDataV();
            Console.WriteLine(",立場:{0}", Position);
        }

        // 抽象メソッドのオーバーライド
        public override void ViewDataA() {
            // base.ViewDataA();   // エラー
            Console.WriteLine("名前:{0},立場:{1}", Name, Position);
        }
    }
}

5~23行目:GCharacterクラスを継承したGEnemyクラスの定義

13~16行目:仮想メソッドのオーバーライド。仮想メソッドの場合は,基底クラスのメソッドを呼び出すことができる(14行目)

19~22行目:抽象メソッドのオーバーライド。抽象メソッドの場合は,基底クラスのメソッドを呼び出すことはできない

csh_sample_133.cs

using System;

namespace csh_sample {
    class csh_sample_133 {
        static void Main(string[] args) {
            GEnemy gEmy = new GEnemy("Aくん", "ボス");

            gEmy.ViewDataV();
            gEmy.ViewDataA();
        }
    }
}

6行目:GEnemyクラスのインスタンスを生成し,変数gEmyに代入(基底クラスのコンストラクターも呼び出される)

8行目:仮想メソッドをオーバーライドしたメソッドの呼び出し

9行目:抽象メソッドをオーバーライドしたメソッドの呼び出し

実行結果

名前:Aくん,立場:ボス
名前:Aくん,立場:ボス

その他

インタフェース

 インタフェースとは,クラスの作成者と使用者の間の規約で,どのメソッドにどのような引数を渡すのかを定めたものです。抽象メソッドを使用して,メソッドの実装を強制することができます。

※ 抽象クラスと違い,抽象メソッドのみを持つことができる。抽象クラス,抽象メソッドの詳細は上記参照

※ クラスと違い,多重継承(複数のインタフェースを継承)できる

インタフェースの定義

 インタフェースは,次のように定義します。

interface インタフェース名 {
    public 抽象メソッドの宣言
}

※ メンバー変数は持てない

※ 抽象メソッドのアクセス修飾子は「public」のみ使用可能

※ 静的メソッドは持てない

インタフェースの実装

 インタフェースは,次のように実装します。

class クラス名 : インタフェース名 {
    クラスの定義
}

例)インタフェースの定義と実装

IViewData.cs

namespace csh_sample {
    // IViewDataインタフェースの定義
    interface IViewData {
        public abstract void ViewName(string _str);
    }
}

3~5行目:インタフェースの定義

4行目:抽象メソッドの宣言(実装は持たない)

ICalcData.cs

namespace csh_sample {
    // ICalcDataインタフェースの定義
    interface ICalcData {
        public abstract int IncreaseHP(int _n);
    }
}

3~5行目:インタフェースの定義

4行目:抽象メソッドの宣言(実装は持たない)

GCharacter.cs

using System;

namespace csh_sample{
    // GCharacterクラスの定義
    class GCharacter : IViewData, ICalcData{
        public string Name { get; set; }
        public int Hp { get; set; }

        public GCharacter(string _name, int _hp) {
            Name = _name;
            Hp = _hp;
        }

        public void ViewName(string _str) {
            Console.Write("名前:{0}", Name);
        }

        public int IncreaseHP(int _n) {
            Hp += _n;
            return Hp;
        }
    }
}

5~22行目:IViewDataインタフェースとICalcDataインタフェースを実装したクラスの定義(インタフェースは多重継承可能)

14~16行目:IViewDataインタフェースで宣言した抽象メソッドの実装(実装しないとエラーになる。overrideは記述しない

18~21行目:ICalcDataインタフェースで宣言した抽象メソッドの実装(実装しないとエラーになる。overrideは記述しない

csh_sample_134.cs

using System;

namespace csh_sample {
    class csh_sample_134 {
        static void Main(string[] args) {
            GCharacter gChr = new GCharacter("Aくん", 100);

            gChr.ViewName(gChr.Name);
            Console.WriteLine(", HP:{0}", gChr.IncreaseHP(20));
        }
    }
}

6行目:GCharacterクラスのインスタンスを生成し,変数gChrに代入

8行目:IViewDataインタフェースの抽象メソッドを実装したメソッドの呼び出し

9行目:ICalcDataインタフェースの抽象メソッドを実装したメソッドの呼び出し

実行結果

名前:Aくん, HP:120
.NET Frameworkのインタフェース

 .NET Frameworkには,汎用性の高いインタフェースが用意されています。

インタフェース説明
IComparable比較を可能にするメソッドを提供する。他のオブジェクトと比較するために,CompareTo()メソッドを実装する必要がある
IDisposableアンマネージリソースなどを解放するためのメソッドを提供する。リソースを解放するためにDispose()メソッドを実装する必要がある

ジェネリック

 ジェネリックメソッドとは,型だけが違い処理内容が同じメソッドを定義する機能のことをいいます。型を引数として渡し,呼び出し時に具体化します。

 ジェネリッククラスとは,型だけが違うクラスを定義する機能のことをいいます。

※ インタフェースやデリゲート(後述)についても,ジェネリックを定義できる

ジェネリックメソッドの定義

 ジェネリックメソッドは,次のように定義します。

アクセス修飾子 戻り値の型 メソッド名<型引数>(引数1, [引数2], …) where 制約条件 {
    処理;
}

※ 制約条件…型が満たすべき条件。省略可能。詳細は後述

ジェネリッククラスの定義

 ジェネリッククラスは,次のように定義します。

class クラス名<型引数名> where 制約条件 {
    メンバ変数やメソッド(メンバ関数)の定義;
}

※ 制約条件…型が満たすべき条件。省略可能。詳細は後述

 複数の型引数を記述することもできます。

class クラス名<型引数名1, 型引数名2> where 制約条件1, where 制約条件2 {
    メンバ変数やメソッド(メンバ関数)の定義;
}

※ 型引数それぞれに対して制約条件を付けることができる

制約条件

 制約条件には,次のようなものがあります。

ジェネリックの制約条件に関する説明画像

例)ジェネリックメソッドの定義と利用

PCalc.cs

using System;
using System.Numerics;

namespace csh_sample {
    // PCalcクラスの定義
    static class PCalc {
        // 引数で渡された値を入れ替える
        public static void Swap<T>(ref T _t1, ref T _t2) {
            T t = _t1;
            _t1 = _t2;
            _t2 = t;
        }

        // 引数で渡された値の中で,大きい方の値を返す
        public static T Compare<T>(T _t1, T _t2) where T : IComparable {
            return _t1.CompareTo(_t2) > 0 ? _t1 : _t2;
        }
    }
}

6~18行目:静的クラスの定義

8~12行目:ジェネリックメソッド(制約条件なし)の宣言と定義

15~17行目:ジェネリックメソッド(制約条件付き)の宣言と定義。IComparableインタフェースのCompareTo()メソッドを実装しなければならない(IComparableインタフェースの詳細は「付録-.NET Frameworkのインタフェース-IComparable」を参照)

GCharacter.cs

using System;

namespace csh_sample{
    // GCharacterクラスの定義
    class GCharacter {
        public string Name { get; set; }
        public int Hp { get; set; }

        public GCharacter(string _name, int _hp) {
            Name = _name;
            Hp = _hp;
        }
    }
}

csh_sample_135.cs

using System;

namespace csh_sample {
    class csh_sample_135 {
        static void Main(string[] args) {
            GCharacter[] gChr = new GCharacter[2];
            gChr[0] = new GCharacter("Aくん", 100);
            gChr[1] = new GCharacter("Bくん", 120);

            Console.WriteLine("初期化時:");
            foreach(GCharacter g in gChr) {
                Console.WriteLine("名前:{0}, HP:{1}", g.Name, g.Hp);
            }

            Console.WriteLine("入れ替え時:");
            PCalc.Swap<GCharacter>(ref gChr[0], ref gChr[1]);
            // PCalc.Swap<int>(ref gChr[0].hp, ref gChr[1].hp);
            foreach(GCharacter g in gChr) {
                Console.WriteLine("名前:{0}, HP:{1}", g.Name, g.Hp);
            }

            Console.WriteLine("比較後:");
            gChr[0].Hp = PCalc.Compare<int>(gChr[0].Hp, 150);
            foreach(GCharacter g in gChr) {
                Console.WriteLine("名前:{0}, HP:{1}", g.Name, g.Hp);
            }
        }
    }
}

16行目:PCalcクラスのSwap()ジェネリックメソッドの呼び出し。型引数にGCharacterを指定し,引数1と引数2を入れ替えている。Hpのみを入れ替える場合は,17行目のようにする

23行目:PCalcクラスのCompare()ジェネリックメソッドの呼び出し。型引数にintを指定し,引数1と引数2の値の大きい方の値を戻してgChr[0].Hpに代入している

実行結果

初期化時:
名前:Aくん, HP:100
名前:Bくん, HP:120
入れ替え時:
名前:Bくん, HP:120
名前:Aくん, HP:100
比較後:
名前:Bくん, HP:150
名前:Aくん, HP:100

デリゲート(委譲)

 デリゲートとは,メソッドを参照する型のことをいいます。

※ C言語の関数ポインターのようなもので,他のメソッドに処理を委譲する。イベントハンドラなどに利用する

デリゲートの宣言

 デリゲートは,次のように宣言します。

delegate 戻り値の型 デリゲートの型名(引数1, [引数2], …);

※ 引数を受け取り,戻り値の型の値を返すメソッドを代入できるようになる

※ デリゲート型は自動的にSystem.Delegateクラスの派生クラスになる

例)デリゲートの宣言と利用

GCharacter.cs

using System;

namespace csh_sample{
    // GCharacterクラスの定義
    class GCharacter {
        public string Name { get; set; }

        public GCharacter(string _name) {
            Name = _name;
        }

        public void ViewName() {
            Console.WriteLine("{0}", Name);
        }
    }
}

csh_sample_136.cs

using System;

namespace csh_sample {
    delegate int CalcData(int n);
    delegate void ViewData();

    class csh_sample_136 {
        static void Main(string[] args) {
            int iPoint = 100;
            GCharacter gChr1 = new GCharacter("Aくん");
            GCharacter gChr2 = new GCharacter("Bくん");

            // CalcData calc = new CalcData(CalcPoint); とはしない
            CalcData calc = CalcPoint;
            iPoint += calc(10);
            Console.WriteLine("ログインポイント:{0}", iPoint);

            Console.WriteLine("使用可能キャラ:");
            // ViewData view = new ViewData(gChr1.ViewName); とはしない
            // view += new ViewData(gChr2.ViewName); とはしない
            ViewData view = gChr1.ViewName;
            view += gChr2.ViewName;
            view();
        }
        
        static int CalcPoint(int _n) {
            var r = new Random();
            _n = r.Next(1, 5) * _n;
            return _n;
        }
    }
}

4~5行目:デリゲートの宣言

14行目:CalcData型変数calcにCalcPoint()メソッドを代入

15行目:デリゲート(CalcData)経由でCalcPoint()メソッドを呼び出し,変数iPointに戻り値を代入

21~22行目:ViewData型変数viewにGCharacterクラスのViewName()インスタンスメソッドを代入(デリゲートには,複数のメソッドを代入することができる)

23行目:デリゲート(ViewData)経由で,それぞれのViewName()インスタンスメソッドを呼び出す

実行結果

ログインポイント:130
使用可能キャラ:
Aくん
Bくん
イベント

 イベントとは,マウスをクリックしたり,キーボードのキーを押したりしたときに発生する事象のことをいいます。

 イベントハンドラーとは,イベントが発生した際に行う処理のことをいいます。

イベントの宣言

 イベントは,次のように宣言します。

event デリゲート型 イベントハンドラー名;

※ eventは,デリゲートに対するプロパティのようなもの。ただし,デリゲートの呼び出しは,クラス内からのみ可能外部からはデリゲートの追加/削除のみが可能

 プロパティのget/setアクセサーと同じように,追加/削除時の処理を明示する場合は,次のようにします。

event デリゲート型 イベントハンドラー名 {
    add {
        イベントハンドラー追加時の処理(valueという変数にイベントハンドラーが代入されている);
    }
    remove {
        イベントハンドラー削除時の処理;
    }
}

※ add/removeアクセサーを用いて処理を明示的に指定することもできる。省略した場合は,自動生成してくれる

例)イベントの宣言とイベントハンドラの登録,イベントの発生

MyEvent.cs

using System;

namespace csh_sample {
    delegate void MyEventHandler(string str);

    // MyEventクラスの定義
    class MyEvent {
        public event MyEventHandler? Listner;
        public string? str;
        
        public MyEvent(string _str) {
            str = _str;
        }

        public void Start() {
            if (Listner != null) {
                Listner(str!);          // クラス内からのみ呼び出せる
            }
        }
    }
}

4行目:デリゲートの宣言

8行目:イベントの宣言

15~19行目:イベントハンドラ(イベント発生時に行う処理)の定義(Listnerがnullでない場合にイベントを発生させる(17行目))

Game.cs

using System;

namespace csh_sample{
    // Gameクラスの定義
    class Game : IDisposable {
        MyEvent eM;
        
        public Game() {
            Console.Write("いんちきゲーム:");

            eM = new MyEvent("スタート");
            eM.Listner += OnTime;
            // eM.Listner();    // エラー。クラス外からは呼び出せない
            eM.Start();
        }
        
        private void OnTime(string _str) {
            Console.WriteLine(_str);
        }        

        public void Dispose() {
            eM.Listner -= OnTime;
        }
    }
}

12行目:イベントハンドラの登録

17~19行目:イベント発生時に呼び出されるメソッド(OnTime())の定義

22行目:イベントハンドラの登録解除

csh_sample_137.cs

using System;

namespace csh_sample {
    class csh_sample_137 {
        static void Main(string[] args) {
            Game game = new Game();
        }
    }
}

6行目:Gameクラスのインスタンスが生成され,イベントを1回だけ発生させる

実行結果

いんちきゲーム:スタート

例)独自イベントの作成

MyEventArgs.cs

using System;

namespace csh_sample{
    // MyEventArgsクラスの定義
    public class MyEventArgs : EventArgs {
        public DateTime DateTime{get; set;}
    }
}

5~7行目:独自イベントクラスの定義(EventArgsクラスを継承。データを渡せるようにする)

KeyboardEvent.cs

using System;

namespace csh_sample {
    // KeyboardEventクラス(イベント待ち受けクラス)の定義
    class KeyboardEvent {
        public delegate void KeyboardEventHandler(KeyboardEvent k, MyEventArgs e);
        public event KeyboardEventHandler? OnKeyDown;
        public MyEventArgs? e = null;
        public string? str;

        public void Start(CancellationTokenSource _cts) {
            while (!_cts.Token.IsCancellationRequested) {
                str = Console.ReadLine();
                e = new MyEventArgs();
                e.DateTime = DateTime.Now;
                OnKeyDown!(this, e);
            }
        }
    }
}

6行目:デリゲートの宣言(MyEventArgs型のオブジェクトを渡せるようにする)

7行目:イベントの宣言

11~18行目:イベントハンドラ(イベント発生時に行う処理)の定義

12~17行目:タスクがキャンセルされていない間,13行目~16行目の処理を繰り返す(キーボードから入力された文字と現在時刻をデータとして,OnKeyDownイベントを発生させる)

Game.cs

using System;

namespace csh_sample{
    // Gameクラス(イベント処理クラス)の定義
    class Game : IDisposable {
        private KeyboardEvent eKey;
        private CancellationTokenSource cts;

        public Game() {
            ViewMenu();

            cts = new CancellationTokenSource();
            eKey = new KeyboardEvent();
            eKey.OnKeyDown += OnKeyDown;
            Task.WhenAll(
                Task.Run(() => eKey.Start(cts))
                ).Wait();
        }
        
        public void OnKeyDown(KeyboardEvent _k, MyEventArgs _e) {
            switch (_k.str) {
                case "s":
                    Console.WriteLine("*****ゲーム開始:{0}", _e.DateTime);
                    Console.WriteLine("...");
                    System.Threading.Thread.Sleep(1000);
                    Console.WriteLine("...");
                    System.Threading.Thread.Sleep(1000);
                    Console.WriteLine("...");
                    System.Threading.Thread.Sleep(1000);
                    Console.WriteLine("*****ゲーム終了*****");
                    System.Threading.Thread.Sleep(1000);
                    ViewMenu();
                    break;
                case "o":
                    Console.WriteLine("++++++設定画面++++++");
                    Console.WriteLine("...");
                    Console.WriteLine("++++++設定終了++++++");
                    System.Threading.Thread.Sleep(1000);
                    ViewMenu();
                    break;
                default:
                    Console.WriteLine("アプリの終了");
                    cts.Cancel();
                    break;
            }
        }
        
        public void ViewMenu() {
            Console.WriteLine("======メニュー======");
            Console.WriteLine("s      : ゲーム開始");
            Console.WriteLine("o      : 設定");
            Console.WriteLine("その他 : 終了");
            Console.WriteLine("====================");
            Console.Write("コマンド:");
        }
  
        public void Dispose() {
            eKey.OnKeyDown -= OnKeyDown!;
        }
    }
}

12行目:CancellationTokenSourceクラス(タスクのキャンセルを制御するためのクラス)のインスタンスを生成

14行目:イベントハンドラの登録

15~17行目:非同期タスクを作成(Task.Run(…))し,非同期タスクがすべて完了(Task.WhenAll(…).Wait())するまで待機する

20~46行目:イベント発生時に呼び出されるメソッド(OnKeyDown())の定義。キーボードから「s」,「o」以外が入力された場合はタスクをキャンセルする(43行目)

58行目:イベントハンドラの登録解除

csh_sample_138.cs

using System;

namespace csh_sample {
    class csh_sample_138 {
        static void Main(string[] args) {
            Game game = new Game();
        }
    }
}

6行目:Gameクラスのインスタンスを生成

実行結果

======メニュー======
s      : ゲーム開始
o      : 設定
その他 : 終了
====================
コマンド:s
*****ゲーム開始:2025/05/28 12:58:31
...
...
...
*****ゲーム終了*****
======メニュー======
s      : ゲーム開始
o      : 設定
その他 : 終了
====================
コマンド:o
++++++設定画面++++++
...
++++++設定終了++++++
======メニュー======
s      : ゲーム開始
o      : 設定
その他 : 終了
====================
コマンド:
アプリの終了
Action,Func

 ActionやFuncは汎用的なデリゲートです。

Action引数なし,戻り値なしのメソッドを参照する
Action<引数>引数あり,戻り値なしのメソッドを参照する。複数の引数を指定する場合は,Action<引数1,引数2,…>とする
Func<戻り値>引数なし,戻り値ありのメソッドを参照する
Func<引数,戻り値>引数あり,戻り値ありのメソッドを参照する。複数の引数を指定する場合は,Func<引数1,引数2,…,戻り値>とする

例)デリゲートの宣言と利用の例をActionとFuncを使用して書き換える

GCharacter.cs

using System;

namespace csh_sample{
    // GCharacterクラスの定義
    class GCharacter {
        public string Name { get; set; }

        public GCharacter(string _name) {
            Name = _name;
        }

        public void ViewName() {
            Console.WriteLine("{0}", Name);
        }
    }
}

csh_sample_136_2.cs

using System;

namespace csh_sample {
    class csh_sample_136_2 {
        static void Main(string[] args) {
            int iPoint = 100;
            GCharacter gChr1 = new GCharacter("Aくん");
            GCharacter gChr2 = new GCharacter("Bくん");

            Func<int, int> fCalc = CalcPoint;
            iPoint += fCalc(10);
            Console.WriteLine("ログインポイント:{0}", iPoint);

            Console.WriteLine("使用可能キャラ:");
            Action aView = gChr1.ViewName;
            aView += gChr2.ViewName;
            aView();
        }
        
        static int CalcPoint(int _n) {
            var r = new Random();
            _n = r.Next(1, 5) * _n;
            return _n;
        }
    }
}

10行目:CalcPoint()メソッドを参照するデリゲートを生成

15~16行目:GCharacterクラスのViewName()インスタンスメソッドを参照するデリゲートを生成(デリゲートには,複数のメソッドを代入することができる)

実行結果

ログインポイント:130
使用可能キャラ:
Aくん
Bくん
匿名メソッド

 匿名メソッドとは,名前のないメソッドのことをいいます。匿名メソッドを使用すると,処理をインラインで記述することができます。

※ 処理をインラインで記述できるので,コードの散乱を防ぐことができる

例)デリゲートの宣言と利用の例をActionとFuncを使用して書き換えた例を,さらに,匿名メソッドを使用して書き換える

GCharacter.cs

using System;

namespace csh_sample{
    // GCharacterクラスの定義
    class GCharacter {
        public string Name { get; set; }

        public GCharacter(string _name) {
            Name = _name;
        }
    }
}

※ ViewName()メソッドを削除

csh_sample_136_3.cs

using System;

namespace csh_sample {
    class csh_sample_136_3 {
        static void Main(string[] args) {
            int iPoint = 100;
            GCharacter gChr1 = new GCharacter("Aくん");
            GCharacter gChr2 = new GCharacter("Bくん");

            Func<int, int> fCalc = delegate(int _n) {
                var r = new Random();
                _n = r.Next(1, 5) * _n;
                return _n;
            };
            iPoint += fCalc(10);
            Console.WriteLine("ログインポイント:{0}", iPoint);

            Console.WriteLine("使用可能キャラ:");
            Action aView = delegate() {Console.WriteLine("{0}", gChr1.Name);};
            aView += delegate() {Console.WriteLine("{0}", gChr2.Name);};
            aView();
        }
    }
}

10~14,19,20行目:匿名メソッドを使用してデリゲートを生成

実行結果

ログインポイント:130
使用可能キャラ:
Aくん
Bくん
ラムダ式

 ラムダ式は,匿名メソッドを簡潔に記述するために使用する記述方式です。

 例えば,次のような匿名メソッドがあったとします。

Func<int, int> fCalc = delegate(int _n) {
    var r = new Random();
    _n = r.Next(1, 5) * _n;
    return _n;
};

 これをラムダ式で書くと,次のようになります。

Func<int, int> fCalc = (int _n) => {
    var r = new Random();
    _n = r.Next(1, 5) * _n;
    return _n;
};

※ delegateを削除して,=>(ラムダ演算子)を使用する(=>演算子の左側は引数,右側は処理を表す)

 この式は,さらに短くすることができます。

Func<int, int> fCalc = _n => new Random().Next(1, 5) * _n;

※ =>演算子の右側の式の結果が戻り値になる(処理が1つの場合は,returnや{}を省略できる)

 引数がない場合は,次のように記述します。

Action aView = () => Console.WriteLine("{0}", gChr1.Name);

※ 引数がない場合は,=>演算子の左側に()と書く

例)デリゲートの宣言と利用の例をActionとFuncを使用して書き換えた例を匿名メソッドを使用して書き換えた例を,さらに,ラムダ式を使用して書き換える

GCharacter.cs

using System;

namespace csh_sample{
    // GCharacterクラスの定義
    class GCharacter {
        public string Name { get; set; }

        public GCharacter(string _name) {
            Name = _name;
        }
    }
}

※ ViewName()メソッドを削除

csh_sample_136_3.cs

using System;

namespace csh_sample {
    class csh_sample_136_4 {
        static void Main(string[] args) {
            int iPoint = 100;
            GCharacter gChr1 = new GCharacter("Aくん");
            GCharacter gChr2 = new GCharacter("Bくん");

            Func<int, int> fCalc = _n => new Random().Next(1, 5) * _n;
            iPoint += fCalc(10);
            Console.WriteLine("ログインポイント:{0}", iPoint);

            Console.WriteLine("使用可能キャラ:");
            Action aView = () => Console.WriteLine("{0}", gChr1.Name);
            aView += () => Console.WriteLine("{0}", gChr2.Name);
            aView();
        }
    }
}

10,15,16行目:ラムダ式を使用してデリゲートを生成

実行結果

ログインポイント:130
使用可能キャラ:
Aくん
Bくん

非同期処理

同期処理と非同期処理

 同期処理とは,複数の処理を1つずつ順番に実行していく方式のことをいいます。

 非同期処理とは,複数の処理を並行して実行していく方式のことをいいます。負荷が高く時間のかかる処理や,I/O待ちなどの処理を別スレッドで行うことで,応答性を高めることができます。

asyncとawait

 非同期メソッドを定義する場合はasyncを使用します。awaitは,非同期メソッド内で非同期処理の完了を待つ場合に使用します。

※ asyncやawaitを使用すると,非同期処理を同期処理のように記述することができる

例)非同期メソッドの利用

Calc.cs

using System;

namespace csh_sample {
    // Calcクラスの定義
    class Calc {
        // 非同期メソッド(重い処理を別スレッドで実行するため,非同期にする)
        public static async Task<int> HeavyProcessing(int _n) {
            int i = 0;
            Console.WriteLine("重い処理:スタート");
            while(i < _n) {
                await Task.Delay(1200);
                Console.WriteLine("重い処理:{0}回目終了", i + 1);
                i++;
            }
            return i;
        }

        // 同期(ふつうの)メソッド
        public static int LightProcessing(int _n) {
            int i = 0;
            Console.WriteLine("軽い処理:スタート");
            while(i < _n) {
                Thread.Sleep(500);
                Console.WriteLine("軽い処理:{0}回目終了", i + 1);
                i++;
            }
            return i;
        }
    }
}

7~16行目:非同期メソッドの定義(このメソッドは,別スレッドで実行される)。戻り値が必要ない場合はTaskを,戻り値が必要な場合はTask<T>(Tに任意の型を指定する)を返すようにする(Task型,または,Task<T>型を返すことで,非同期メソッドの状態や戻り値などを知ることができる)。イベントハンドラの場合は,「async void」を返すようにする(「async void」にすると,タスクの状態を把握できないため,なるべく使用しないようにする)

11行目:非同期にしたい重い処理(ここでは,1,200ミリ秒待つという処理)にawaitを付ける(awaitは,Task型を返すメソッド(ここでは,「Task.Delay(1200)」)のみに使用可能。また,awaitを付けた場合は,非同期処理が終わるまで次の行に進まない)

csh_Asynchronous_01.cs

using System;

namespace csh_sample {
    class csh_Asynchronous_01 {
        static void Main(string[] args) {
            Task<int> hTask = Calc.HeavyProcessing(2);
            int lTask = Calc.LightProcessing(3);
            Console.WriteLine("重い処理の実行回数:{0}", hTask.Result);
            Console.WriteLine("軽い処理の実行回数:{0}", lTask);
        }
    }
}

6行目:非同期メソッドを実行(このメソッドは,別スレッドで実行される)

8行目:非同期メソッドの戻り値を取得してコンソールに表示する

実行結果

重い処理:スタート
軽い処理:スタート
軽い処理:1回目終了
軽い処理:2回目終了
重い処理:1回目終了
軽い処理:3回目終了
重い処理:2回目終了
重い処理:3回目終了
重い処理の実行回数:3
軽い処理の実行回数:3

その他

Dictionary

 Dictionaryとは,キー(Key)と値(Value)のペアを格納するデータ構造(連想配列)のことをいいます。

※ キーを使用して値にアクセスする

※ 同じキーを使用することはできない

Dictionaryの宣言と定義

 Dictionaryは,次のように宣言・定義します。

using System.Collections.Generic;

Dictionary<キーのデータ型, 値のデータ型> 変数名 = new Dictionary<キーのデータ型, 値のデータ型>();

※ Dictionaryを宣言する際は,先頭にusingディレクティブ(「using System.Collections.Generic;」)を追加する

 Dictionaryの宣言時に値を代入して初期化することもできます。

using System.Collections.Generic;

Dictionary<キーのデータ型, 値のデータ型> 変数名 = new Dictionary<キーのデータ型, 値のデータ型>() {
    {キー1, 値1},
    {キー2, 値2},
    …
};

 各要素の値は,キーを指定して参照します。

変数名[キー];

要素の追加・削除

 Dictionaryに要素を追加する場合は,次のようにします。

変数名.Add(キー, 値);

※ 同じキーを使用することはできない

 Dictionaryの要素を削除する場合は,次のようにします。

変数名.Remove(キー);

要素の検索

 Dictionaryの要素を検索する場合は,次のようにします。

変数名.ContainsKey(キー);    // キーで検索。キーが存在する場合はtrueを,存在しない場合はfalseを返す
変数名.ContainsValue(値);    // 値で検索。値が存在する場合はtrueを,存在しない場合はfalseを返す

要素の並べ替え

 Dictionaryの要素を並べ替える場合は,次のようにします。

var y = 変数名.OrderBy(x => x.Key);                  // キーの昇順で並べ替え
var y = 変数名.OrderByDescending(x => x.Value);      // 値の降順で並べ替え

例)Dictionaryの使用方法

csh_Dictionary_01.cs

using System;
using System.Collections.Generic;

namespace csh_sample {
    class csh_Dictionary_01 {
        static void Main(string[] args) {
            var myDic = new Dictionary<string, string>();
            myDic.Add("GAT-X102", "デュエルガンダム");
            myDic.Add("GAT-X103", "バスターガンダム");
            myDic.Add("GAT-X303", "イージスガンダム");
            myDic.Add("GAT-X207", "ブリッツガンダム");
            myDic.Add("GAT-X105", "ストライクガンダム");
            myDic.Remove("GAT-X207");
            myDic["GAT-X102"] = "デュエルガンダム アサルトシュラウド";
            var sortDic = myDic.OrderBy(x => x.Key);
            
            foreach(KeyValuePair<string, string> item in sortDic) {
                Console.WriteLine("{0} {1}", item.Key, item.Value);
            }
        }
    }
}

2行目:usingディレクティブ。Dictionaryを使用する場合,「System.Collections.Generic;」と記述する

7行目:Dictionaryの宣言と定義

8~12行目:Dictionaryに要素を追加

13行目:Dictionaryから要素を削除

14行目:Dictionaryの要素を更新

15行目:Dictionaryの要素をキー(Key)の昇順に並べ替え

17~19行目:Dictionaryのすべての要素のキー(Key)と値(Value)を表示

実行結果

GAT-X102 デュエルガンダム アサルトシュラウド
GAT-X103 バスターガンダム
GAT-X105 ストライクガンダム
GAT-X303 イージスガンダム

列挙型

 曜日など,特定の値だけを持つものは列挙型を利用すると便利です。

列挙型の定義

 列挙型は,次のように定義します。

enum 列挙型名 {
    メンバー1, メンバー2, … , メンバーn
}

※ メンバー名は,日本語で書くこともできる

※ 内部では整数値で扱われる(0,1,…,n)

 列挙型を定義する際に,値を指定することもできます。

enum 列挙型名 {
    メンバー1 = 23,
    メンバー2 = 33,
    メンバー3,
    …
    メンバーn
}

※ メンバー3以降の値は,メンバー2の値に1ずつ加えた値になる

列挙型の利用

 列挙型を利用する場合は,次のようにします。

列挙型名.メンバー名;

例)列挙型の使用方法

GCharacter.cs

using System;

namespace csh_sample {
    // Work列挙型の定義
    enum Work {
        Hero, Wizard, Other
    }

    // GCharacterクラスの定義
    class GCharacter {
        private string Name { get; set; }
        private Work Work { get; set; }

        public GCharacter(string _name, Work _work) {
            Name = _name;
            Work = _work;
        }
        
        public void ViewData() {
            Console.WriteLine("{0}, 職業:{1}", Name, Work);
        }
    }
}

5~7行目:列挙型の定義

12行目:Work列挙型プロパティWorkの宣言

16行目:Workプロパティに値を代入(初期化)

20行目:Workプロパティの値を表示

csh_sample_201.cs

using System;

namespace csh_sample {
    class csh_sample_201 {
        static void Main(string[] args) {
            GCharacter gChar = new GCharacter("Cくん", Work.Wizard); 

            gChar.ViewData();
        }
    }
}

6行目:GCharacterクラスのインスタンスを生成し,変数gCharに代入(メンバ変数が初期化される)。コンストラクターの2番目の引数にWork列挙型の値を渡している

8行目:メソッドの呼び出し。名前と職業(Work列挙型の値)が表示される

実行結果

Cくん, 職業:Wizard

フラグとしての利用

 列挙型をフラグとして利用する場合は,列挙型の値を2のべき乗の値とし,OR演算で複数の値を組み合わせて使用できるようにします。

※ 2進数の詳細は「2進数,8進数,16進数 -情報処理シンプルまとめ」を参照

※ OR演算の詳細は「集合と論理演算・論理回路 -情報処理シンプルまとめ」を参照

例)列挙型をフラグとして使用する方法

GCharacter.cs

using System;

namespace csh_sample {
    // Magic列挙型の定義
    [Flags]
    enum Magic {
        None = 0,           // 0000 0000 (2)
        FireMagic = 1,      // 0000 0001 (2)
        WaterMagic = 2,     // 0000 0010 (2)
        HealingMagic = 4    // 0000 0100 (2)
    }

    // GCharacterクラスの定義
    class GCharacter {
        private string Name { get; set; }
        private Magic Magic { get; set; }

        public GCharacter(string _name, Magic _magic) {
            Name = _name;
            Magic = _magic;
        }
        
        public void ViewData() {
            Console.WriteLine("{0}, 職業:{1}", Name, Magic);
        }
    }
}

5~11行目:列挙型の定義。Flags属性を付けている(詳細は後述の実行結果で)

csh_sample_202.cs

using System;

namespace csh_sample {
    class csh_sample_202 {
        static void Main(string[] args) {
            GCharacter gChar1 = new GCharacter("Aくん", Magic.FireMagic); 
            GCharacter gChar2 = new GCharacter("Bくん", Magic.None); 
            GCharacter gChar3 = new GCharacter("Cくん", Magic.FireMagic | Magic.WaterMagic | Magic.HealingMagic); 

            gChar1.ViewData();
            gChar2.ViewData();
            gChar3.ViewData();
        }
    }
}

6~8行目:GCharacterクラスのインスタンスを生成し,各プロパティに値を代入(メンバ変数が初期化される)。コンストラクターの2番目の引数にWork列挙型の値を渡している(Cくんは複数の魔法を使えるようにしている)

10~12行目:メソッドの呼び出し。名前と職業(Work列挙型の値)が表示される

実行結果

Aくん, 職業:FireMagic
Bくん, 職業:None
Cくん, 職業:FireMagic, WaterMagic, HealingMagic

列挙型にFlags属性を付けていない場合の実行結果は,次のようになる。

Aくん, 職業:FireMagic
Bくん, 職業:None
Cくん, 職業:7

※ 値が7のメンバーは列挙型に定義していないため数値が表示される

構造体

構造体の定義

 構造体は,次のように定義します。

struct 構造体名 {
   メンバ変数やメソッド(メンバ関数)の定義
}

※ クラスとは違い,継承デフォルトコンストラクター(引数なしのコンストラクター)の定義ファイナライザーの定義フィールド初期化子の使用できない

※ 構造体は,小さなオブジェクトを扱う場合に有利(クラスは大きなオブジェクトを扱う場合に有利)

※ 構造体は値型なので,宣言時にnewは使用しない(使用してもよい)

例)構造体の定義と利用

MyPoint.cs

using System;

namespace csh_sample {
    // MyPoint構造体の定義
    struct MyPoint {
        public int X { get; set; }
        public int Y { get; set; }

        public MyPoint(int _x, int _y) {
            X = _x;
            Y = _y;
        }
    }
}

6~7行目:プロパティの定義

9~12行目:コンストラクタ(引数あり)の定義

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    class GCharacter {
        private string Name { get; set; }
        private MyPoint mPoint;

        public GCharacter(string _name, MyPoint _mPoint) {
            Name = _name;
            mPoint = _mPoint;
        }
        
        public void Move(int _x, int _y) {
            mPoint.X += + _x;
            mPoint.Y += + _y;
        }

        public void ViewPoint() {
            Console.WriteLine("{0} ({1}, {2})", Name, mPoint.X, mPoint.Y);
        }
    }
}

7行目:MyPoint構造体型変数を宣言(newは使用しなくてもよい)

csh_sample_301.cs

using System;

namespace csh_sample {
    class csh_sample_301 {
        static void Main(string[] args) {
            GCharacter gChar = new GCharacter("Aくん", new MyPoint(10, 7));

            Console.Write("移動前の座標:");
            gChar.ViewPoint();
            gChar.Move(-4, 2);              // x軸方向に-4,y軸方向に2移動
            Console.Write("移動後の座標:");
            gChar.ViewPoint();
        }
    }
}

実行結果

移動前の座標:Aくん (10, 7)
移動後の座標:Aくん (6, 9)

値型と参照型

 値型の変数は値を直接格納します。参照型の変数はオブジェクトへの参照を格納します。

値型と参照型に関する説明画像

例)値型と参照型の比較

MyPoint.cs

using System;

namespace csh_sample {
    // MyPoint構造体の定義
    struct MyPoint_S {
        public int X { get; set; }

        public MyPoint_S(int _x) {
            X = _x;
        }
    }

    // MyPointクラスの定義
    class MyPoint_C {
        public int X { get; set; }

        public MyPoint_C(int _x) {
            X = _x;
        }
    }
}

5~11行目:構造体(値型)の定義

14~20行目:クラス(参照型)の定義

csh_sample_302.cs

using System;

namespace csh_sample {
    class csh_sample_302 {
        static void Main(string[] args) {
            MyPoint_S s1 = new MyPoint_S(2);
            MyPoint_S s2 = s1;
            MyPoint_C c1 = new MyPoint_C(3);
            MyPoint_C c2 = c1;

            Console.WriteLine("変更前:s1={0}, s2={1}, c1={2}, c2={3}", s1.X, s2.X, c1.X, c2.X);
            s2.X = 5;
            c2.X = 7;
            Console.WriteLine("変更前:s1={0}, s2={1}, c1={2}, c2={3}", s1.X, s2.X, c1.X, c2.X);
        }
    }
}

12行目:値型の変数s2に数値を代入 ⇒ s2の値のみが変更される

13行目:参照型の変数c2に数値を代入 ⇒ c2の値だけでなく,c1の値も変更される(同じオブジェクトを参照しているため)

実行結果

変更前:s1=2, s2=2, c1=3, c2=3
変更前:s1=2, s2=5, c1=7, c2=7

null許容型

null

 nullは,何も参照していない無効な状態を表す特別な値です。

null許容型

 null許容型は,null値をもつことができる型です。

int a = null;        // エラー
int? b = null;

※ nullを使用する場合は,「int?」のように「?」を付けてnull許容型にする

null条件演算子(?.,?[])

 null条件演算子を使用すると,nullの場合にnullを返すという処理を楽に記述できます。

string? sName1 = gChr?.Name;
string? sName2 = gChrArray?[0].Name;

※ 「gChr?.Name」や「gChrArray?[0].Name」がnullの場合は,nullを左辺の変数に代入する

 ちなみに,次のコードは,上の1行目と同じ意味になります。

string? sName1;
if(gChr.Name == null) {
    sName1 = null;
} else {
    sName1 = gChr.Name;
}

null合体演算子(??)

 null合体演算子を使用すると,nullの場合に適当な値を返すという処理を楽に記述できます。

string sName1 = gChr.Name ?? "-";

※ 「gChr?.Name」がnullの場合は,「-」を左辺の変数に代入する

 ちなみに,次のコードは,上の1行目と同じ意味になります。

string sName1;
if(gChr.Name == null) {
    sName1 = "-";
} else {
    sName1 = gChr.Name;
}

例外処理

 例外とは,プログラム内で起こってはいけないことが起こってしまうことをいい,その場合に例外処理が必要になります。

例外のキャッチ(try,catch,finally)

 例外が発生する可能性のある処理を記述する場合は,次のようにします。

try {
    // 例外が発生する可能性のある処理
} catch (例外の種類) {
    // 例外発生時の処理
} finally {
    // (例外発生の有無によらない)必ず実行したい処理(リソースの破棄など)
}

※ リソースの破棄(finally)に関する詳細は,「.NET Frameworkのインタフェース-IDisposable」を参照

例)例外のキャッチ(try-catch-finallyの例)

csh_sample_801.cs

using System;

namespace csh_sample {
    // Checkクラスの定義
    static class Check {
        public static int NumCheckS(string _str) {
            // 引数として渡された文字列を数値に変換できない場合に例外が発生する
            try {
                return Int32.Parse(_str);
            } catch (FormatException) {
                Console.WriteLine("例外発生:数値以外が入力されました。");
                return 0;
            } finally {
                // 省略
            }
        }
    }

    class csh_sample_801 {
        static void Main(string[] args) {
            string str = "12a4";
            int n = Check.NumCheckS(str);
        }
    }
}

実行結果

例外発生:数値以外が入力されました。

例外のスロー(throw)

 例外をスローする場合は,次のようにします。

throw System.Exceptionクラスの派生クラスのインスタンス;

※ System.Exceptionクラスの派生クラスのインスタンス以外をスローすることはできない

例)例外のスロー(throwの例)

csh_sample_802.cs

using System;

namespace csh_sample {
    // Checkクラスの定義
    static class Check {
        public static int NumCheckC(char _chr) {
            // 引数として渡された文字が0~9の範囲でない場合に例外をスローする
            if (_chr > '0' && _chr < '9') {
                return (int)_chr;
            } else {
                throw new FormatException();
            }
        }
    }

    class csh_sample_802 {
        static void Main(string[] args) {
            char chr = 'b';
            int n = 0;

            try {
                n = Check.NumCheckC(chr);
            } catch (Exception _e) {
                Console.WriteLine("{0}", _e);
            } finally {
                // 省略
            }
        }
    }
}

実行結果

System.FormatException: One of the identified items was in an invalid format.

usingステートメント

 usingステートメントを使用すると,IDisposableインターフェースを実装するリソースの解放時に,自動的にDispose()メソッドを呼び出してくれます。

※ アンマネージリソースなどの解放忘れを防ぐことができる

※ try-catch-finallyのfinallyの記述を省略できる

※ IDisposableインターフェースの詳細は,「.NET Frameworkのインタフェース-IDisposable」を参照

using(IDisposableインタフェースを実装しているクラス 変数名 = new コンストラクタ―()) {
    処理;
}

付録

.NET Frameworkのインタフェース

IComparable

 IComparableインタフェースは,比較を可能にするメソッドを提供します。他のオブジェクトと比較するために,CompareTo()メソッドを実装する必要があります。

例)IComparableインタフェースの実装

GCharacter.cs

using System;

namespace csh_sample{
    // GCharacterクラスの定義
    class GCharacter : IComparable<GCharacter> {
        public string Name { get; set; }
        public int Hp { get; set; }
        public int Mp { get; set; }

        public GCharacter(string _name, int _hp, int _mp) {
            Name = _name;
            Hp = _hp;
            Mp = _mp;
        }

        // CompareTo()メソッドは,インスタンスのHpが
        // _gChrのHpよりも後ろにあれば1以上の整数値を,
        // _gChrのHpよりも前にあれば-1以下の整数値を,
        // _gChrのHpと同じであれば0を返す
        // このメソッドは,適当なタイミングで自動的に呼び出される
        public int CompareTo(GCharacter? _gChr) {
            return (Hp + Mp).CompareTo(_gChr!.Hp + _gChr!.Mp);
        }
    }
}

csh_sample_IComparable.cs

using System;

namespace csh_sample {
    class csh_sample_IComparable {
        static void Main(string[] args) {
            GCharacter[] gChr = new GCharacter[4];
            gChr[0] = new GCharacter("Aくん", 100,  70);
            gChr[1] = new GCharacter("Bくん", 150,  10);
            gChr[2] = new GCharacter("Cくん",  45, 105);
            gChr[3] = new GCharacter("Dくん",  95,  55);

            // gChrの要素を(HP+MP)の降順(CompareTo()メソッドの実行結果)で,
            // 同値の場合はHPの降順に並べ替え,値を表示する
            foreach(GCharacter g in gChr.OrderByDescending(x => x).ThenByDescending(x => x.Hp)) {
                Console.WriteLine("名前:{0}, HP:{1}, MP:{2}, Total:{3}",
                                  g.Name, g.Hp, g.Mp, g.Hp + g.Mp);
            }
        }
    }
}

※ LINQのメソッドを使用して並べ替える(昇順で並べ替える場合はOrderBy()メソッドを,降順で並べ替える場合はOrderByDescending()メソッドを使用する。また,並べ替えのキーが複数ある場合は,ThenBy()メソッド(昇順の場合)やThenByDescending()メソッド(降順の場合)を追加する)

実行結果

名前:Aくん, HP:100, MP:70, Total:170
名前:Bくん, HP:150, MP:10, Total:160
名前:Dくん, HP:95, MP:55, Total:150
名前:Cくん, HP:45, MP:105, Total:150

IDisposable

 IDisposableインタフェースは,アンマネージリソースなどを解放するためのメソッドを提供します。リソースを解放するためにDispose()メソッドを実装する必要があります。

※ ガーベジコレクションで適切に解放できないリソースなどを解放する

例)IDisposableインタフェースの実装(マネージリソースとアンマネージリソースの解放)

BClass.cs

using System;

namespace csh_sample {
    // BClassクラスの定義
    class BClass : IDisposable {
        private Stream? m;          // Stream(マネージ。IDisposableを実装)
        private IntPtr um;          // 32ビットのポインター(アンマネージ)
        private bool bFlg = false;  // Dispose()メソッドを1回だけ処理させるためのフラグ

        // コンストラクター
        public BClass() {
            // リソース取得部分は省略
        }

        // ファイナライザー
        ~BClass() {
            this.Dispose(false);        // ファイナライザーから呼び出す場合:false
        }

        // Dispose()メソッド
        public void Dispose() {
            this.Dispose(true);         // Dispose()メソッドから呼び出す場合:true
            GC.SuppressFinalize(this);  // ガーベジコレクションによる処理を行わない
        }

        // Dispose(bool)メソッド(仮想メソッド)
        protected virtual void Dispose(bool _dispose) {
            // Dispose(bool)メソッドの呼び出しが1回めの場合
            if (!bFlg) {
                // アンマネージリソースを解放する
                Release(this.um);
                
                // Dispose()メソッドから呼び出された場合にマネージリソースを解放する
                if (_dispose) {
                    if (m != null) {
                        m.Dispose();
                    }
                    Console.WriteLine("マネージリソース(基底クラス)を解放しました");
                }
                
                // フラグをtrueにしてDispose()メソッドを呼び出せないようにする
                bFlg = true;
            }
        }

        // Release()メソッド
        protected static void Release(IntPtr _um) {
            Console.WriteLine("アンマネージリソースを解放しました");
        }
    }
}

DClass.cs

using System;

namespace csh_sample {
    // DClassクラスの定義
    class DClass : BClass {
        private Stream? m2;          // Stream(IDisposableを実装)
        private IntPtr um2;          // 32ビットのポインター(アンマネージ)
        private bool bFlg = false;   // Dispose()メソッドを1回だけ処理させるためのフラグ

        // コンストラクター
        public DClass() {
            // リソース取得部分は省略
        }

        // ファイナライザーは不要
        // ~BClass() {}

        // Dispose(bool)メソッドをオーバーライドする(Dispose()メソッドはオーバーライドしない)
        protected override void Dispose(bool _dispose) {
            // Dispose(bool)メソッドの呼び出しが1回目の場合
            if (!bFlg) {
                // アンマネージリソースを解放する
                Release(um2);
                // Dispose()メソッドから呼び出された場合にマネージリソースを解放する
                if (_dispose) {
                    if (m2 != null) {
                        m2.Dispose();
                    }
                    Console.WriteLine("マネージリソース(派生クラス)を解放しました");
                }
                // フラグをtrueにしてDispose()メソッドを呼び出せないようにする
                bFlg = true;
                // 基底クラスのDispose(bool)メソッドを呼び出す
                base.Dispose(_dispose);
            }
        }
    }
}

csh_sample_IDisposable_2.cs

using System;

namespace csh_sample {
    class csh_sample_IDisposable_2 {
        static void Main(string[] args) {
            DClass? d = new DClass();
            
            try {
                // 処理は省略
            } catch {
                Console.WriteLine("例外が発生");
            } finally {
                if(d != null) {
                    d.Dispose();
                }
            }
        }
    }
}

実行結果

アンマネージリソースを解放しました
マネージリソース(派生クラス)を解放しました
アンマネージリソースを解放しました
マネージリソース(基底クラス)を解放しました

例)IDisposableインタフェースの実装(マネージリソースを解放する)

BClass.cs

using System;

namespace csh_sample {
    // BClassクラスの定義
    class BClass : IDisposable {
        private Stream? m;          // Stream(マネージ。IDisposableを実装)
        private bool bFlg = false;  // Dispose()メソッドを1回だけ処理させるためのフラグ

        // コンストラクター
        public BClass() {
            // リソース取得部分は省略
        }

        // Dispose()メソッド
        public void Dispose() {
            this.Dispose(true);         // Dispose()メソッドから呼び出す場合:true
        }

        // Dispose(bool)メソッド(仮想メソッド)
        protected virtual void Dispose(bool _dispose) {
            // Dispose(bool)メソッドの呼び出しが1回めの場合
            if (!bFlg) {
                // Dispose()メソッドから呼び出された場合にマネージリソースを解放する
                if (_dispose) {
                    if (m != null) {
                        m.Dispose();
                    }
                    Console.WriteLine("マネージリソース(基底クラス)を解放しました");
                }

                // フラグをtrueにしてDispose()メソッドを呼び出せないようにする
                bFlg = true;
            }
        }
    }
}

DClass.cs

using System;

namespace csh_sample {
    // DClassクラスの定義
    class DClass : BClass {
        private Stream? m2;          // Stream(IDisposableを実装)
        private bool bFlg = false;   // Dispose()メソッドを1回だけ処理させるためのフラグ

        // コンストラクター
        public DClass() {
            // リソース取得部分は省略
        }

        // Dispose(bool)メソッドをオーバーライドする(Dispose()メソッドはオーバーライドしない)
        protected override void Dispose(bool _dispose) {
            // Dispose(bool)メソッドの呼び出しが1回目の場合
            if (!bFlg) {
                // Dispose()メソッドから呼び出された場合にマネージリソースを解放する
                if (_dispose) {
                    if (m2 != null) {
                        m2.Dispose();
                    }
                    Console.WriteLine("マネージリソース(派生クラス)を解放しました");
                }
                // フラグをtrueにしてDispose()メソッドを呼び出せないようにする
                bFlg = true;
                // 基底クラスのDispose(bool)メソッドを呼び出す
                base.Dispose(_dispose);
            }
        }
    }
}

csh_sample_IDisposable.cs

using System;

namespace csh_sample {
    class csh_sample_IDisposable {
        static void Main(string[] args) {
            DClass? d = new DClass();
            
            try {
                // 処理は省略
            } catch {
                Console.WriteLine("例外が発生");
            } finally {
                if(d != null) {
                    d.Dispose();
                }
            }
        }
    }
}

実行結果

マネージリソース(派生クラス)を解放しました
マネージリソース(基底クラス)を解放しました

ファイル操作

テキストファイルの読み書き

 テキストファイルを読み書きする場合は,StreamReader,StreamWriterを使用します。

例)テキストファイルの読み書き(書き込み時にフォルダーが存在しない場合は作成する)

MyFile.cs

using System;
using System.IO;
using System.Text;

namespace csh_sample {
    // MyFileクラスの定義
    static class MyFile {
        // テキストファイルの読込み
        public static void ReadFile(string _path) {
            try {
                using (StreamReader file = new StreamReader(_path)) {
                    while (file.Peek() != -1) {
                        Console.WriteLine(file.ReadLine());
                    }
                }
            } catch(Exception e) {
                Console.WriteLine(e.Message);
            }
        }

        // テキストファイルの書込み
        public static void WriteFile(string _path, List<string> _text) {
            // フォルダーが存在しない場合は作成する
            string? dpath = Path.GetDirectoryName(_path);
            if(!Directory.Exists(dpath)) {
                Directory.CreateDirectory(dpath!);
            }

            try {
                // 第2引数:true=追記,false=上書き
                using (StreamWriter file = new StreamWriter(_path, false)) {
                    foreach(string str in _text) {
                        file.WriteLine(str);
                    }
                }
            } catch(Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }
}

csh_sample_File_01.cs

using System;

namespace csh_sample {
    class csh_sample_File_01 {
        static void Main(string[] args) {
            string path = @"C:\myfiles\file_01.txt";
            List<string> text = new List<string> {
                "今日は,雨です。",
                "明日は,晴れるかなー。"
            };

            MyFile.WriteFile(path, text);
            MyFile.ReadFile(path);
        }
    }
}

6行目:ファイルパスなどの文字列の前に@を付けると,そのまま文字列として扱うことができる(逐語的文字列。エスケープシーケンスを無視)

実行結果

今日は,雨です。
明日は,晴れるかなー。

任意のデータの読み書き

 任意のデータを読み書きする場合は,任意のデータをJSON化してバイナリ形式で保存するなどします。

例)任意のデータの読み書き(書き込み時にフォルダーが存在しない場合は作成する)

GCharacter.cs

using System;

namespace csh_sample {
    // GCharacterクラスの定義
    class GCharacter {
        public string? Name { get; set; }
        public int Hp { get; set; }

        public GCharacter() {}
        public GCharacter(string _name, int _hp) {
            Name = _name;
            Hp = _hp;
        }

        public void ViewData() {
            Console.WriteLine("{0}, HP:{1}", Name, Hp);
        }
    }
}

※ デフォルトのコンストラクター(9行目)や,プロパティ(6,7行目)がJSON化の対象となる(メンバ変数はJSON化の対象にはならない)

MyFile.cs

using System;
using System.IO;
using System.Text;
using System.Text.Json;

namespace csh_sample {
    // MyFileクラスの定義
    static class MyFile {
        // バイナリ形式でJSONを読込みデコードする
        public static void ReadFileB<T>(string _path, out T _t) {
            string? json = null;
            try {
                using (FileStream f = new FileStream(_path, FileMode.Open))
                using (BinaryReader r = new BinaryReader(f)) {
                    json = r.ReadString();
                }
            } catch (EndOfStreamException) {
            } catch (Exception e) {
                Console.WriteLine(e.Message);
            }

            // jsonのデコード
            _t = JsonSerializer.Deserialize<T>(json!)!;
        }

        // 任意のデータをJSON化しバイナリ形式で書込む
        public static void WriteFileB<T>(string _path, T _t) {
            // フォルダーが存在しない場合は作成する
            string? dpath = Path.GetDirectoryName(_path);
            if(!Directory.Exists(dpath)) {
                Directory.CreateDirectory(dpath!);
            }

            // json化
            string json = JsonSerializer.Serialize(_t);

            try {
                using (BinaryWriter w = new BinaryWriter(File.OpenWrite(_path))) {
                    w.Write(json);
                }
            } catch(Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }
}

csh_sample_File_02.cs

using System;

namespace csh_sample {
    class csh_sample_File_02 {
        static void Main(string[] args) {
            string path = @"C:\myfiles\file_02.data";
            GCharacter[] gChr = new GCharacter[2];
            gChr[0] = new GCharacter("Aくん", 100);
            gChr[1] = new GCharacter("Bくん", 80);

            MyFile.WriteFileB(path, gChr);
            MyFile.ReadFileB(path, out gChr);

            foreach (GCharacter g in gChr) {
                g.ViewData();
            }
        }
    }
}

6行目:ファイルパスなどの文字列の前に@を付けると,そのまま文字列として扱うことができる(逐語的文字列。エスケープシーケンスを無視)

実行結果

Aくん, HP:100
Bくん, HP:80

まとめ

 今回は,オブジェクト指向プログラミング(C#)についてまとめてみました。暇な時に,新しい内容を追加すると思いますので,忘れた頃に,また,来てみてください。