講義メモ

テキスト編:p.310「12.5 イベント」から
ゲーム開発演習:画面遷移、タイマー処理 など

p.310 12.5 イベント

・コンソールアプリケーションにおける Console.ReadLine()メソッドのように、起動したプログラムが動的に情報を得る
 手段がある
・しかし、Console.ReadLine()メソッドは単なる入力待ちであり、プログラム側からの制御はできない
・そこで、あらかじめ「これが起きたらあれをする」という定義をしておき、必要時に動的に呼び出される仕掛として、
 イベント機能が提供されている。
・キー入力、マウスの操作、通信の受信、時間の経過などの「何かが起きた」ことをイベントの発生という。
・イベントに対して処理を実行するものをイベントハンドラという。
 ※Java言語などではイベントリスナといい、C#の書籍においてもイベントリスナとされていることがある。
・C#では主にフォームアプリケーションでイベントを扱うが、コンソールアプリケーションでもデリゲートを用いることで、
 イベント処理の実装が可能。
・例えば、Enterキー以外のキーの入力を即時に受け取るプログラムを記述できる。

p.312 イベントのプログラミング

・コンソールアプリケーションでもデリゲートを用いてイベント処理を実装する場合の例(クラス側):
 ① 戻り値なし、引数なしのデリゲートを宣言する
 ② イベントの発生を担うクラスを定義する
 ③ ②の中に①を用いるイベントフィールドを定義する
  書式: public event デリゲート名 イベント名;
 ④ ②の中にイベントを発生させるメソッドを定義(「Onイベント名」というメソッド名を推奨)
 ⑤ ④の中で「イベント名();」を実行することでイベントを発生させる。
  ただし、イベントフィールドの準備ができていないときに動作するのを避けるために、イベント名がnullではないか
  をチェックすること
・コンソールアプリケーションでもデリゲートを用いてイベント処理を実装する場合の例(Main()側):
 ① イベントの発生により動作させたいメソッドを記述しておく。
 ② main()の中で、イベントの発生を担うクラスのインスタンスを生成。
 ③ main()の中で「②.イベントフィールド」に対して、マルチキャストデリゲートの書式で①を登録
 ④ main()の中で「②.Onイベント名()」メソッドを呼び出すことで、イベントを発生させる
・なお、上記は説明用に、無理矢理イベントを起こさせる構成になっている

p.313 event01.cs

//p.313 event01.cs
using System;
delegate void MyDelegate(); //イベントで用いるデリゲートを宣言
class MyEventClass { //イベントの発生を担うクラス
    public event MyDelegate eventname; //イベントフィールドを定義(デリゲートを利用)
    public void OnEventname() { //イベントを発生させるメソッド
        if (eventname != null) { //イベントフィールドが準備済ならば
            eventname(); //イベントを発生させる
        }
    }
}
class MyClass { //イベントで呼び出されるメソッド①を持つクラス
    public void show() { //イベントで呼び出されるメソッド①
        Console.WriteLine("show");
    }
}
class MyClass2 { //イベントで呼び出されるメソッド②を持つクラス
    public void show2(){ //イベントで呼び出されるメソッド②
        Console.WriteLine("show2!!");
    }
}
class event01 {
    public static void Main() {
        MyClass mc = new MyClass(); //イベントで呼び出されるメソッド①を持つクラス
        MyClass2 mc2 = new MyClass2(); //イベントで呼び出されるメソッド②を持つクラス
        MyEventClass myevent = new MyEventClass(); //イベントの発生を担うクラス
        myevent.eventname += new MyDelegate(mc.show); //イベントで呼び出されるメソッド①を登録
        myevent.eventname += new MyDelegate(mc2.show2); //イベントで呼び出されるメソッド②も登録
        myevent.OnEventname(); //イベントを発生させる
    }
}

アレンジ演習:p.313 event01.cs

・mc.showメソッドと、mc2.show2メソッドを匿名メソッドにして、ラムダ式で記述しよう。
・これにより、MyClass、MyClass2は不要になる。

作成例

//アレンジ演習:p.313 event01.cs
using System;
delegate void MyDelegate(); //イベントで用いるデリゲートを宣言
class MyEventClass { //イベントの発生を担うクラス
    public event MyDelegate eventname; //イベントフィールドを定義(デリゲートを利用)
    public void OnEventname() { //イベントを発生させるメソッド
        if (eventname != null) { //イベントフィールドが準備済ならば
            eventname(); //イベントを発生させる
        }
    }
}
//class MyClass { //【以下削除】イベントで呼び出されるメソッド①を持つクラス
//    public void show() { //イベントで呼び出されるメソッド①
//        Console.WriteLine("show");
//    }
//}
//class MyClass2 { //【以下削除】イベントで呼び出されるメソッド②を持つクラス
//    public void show2(){ //イベントで呼び出されるメソッド②
//        Console.WriteLine("show2!!");
//    }
//}
class event01 {
    public static void Main() {
        //MyClass mc = new MyClass(); //【削除】イベントで呼び出されるメソッド①を持つクラス
        //MyClass2 mc2 = new MyClass2(); //【削除】イベントで呼び出されるメソッド②を持つクラス
        MyEventClass myevent = new MyEventClass(); //イベントの発生を担うクラス
        myevent.eventname += () => Console.WriteLine("show"); //【変更】イベントで呼び出されるメソッド①を登録
        myevent.eventname += () => Console.WriteLine("show2!!"); //【変更】イベントで呼び出されるメソッド②を登録
        myevent.OnEventname(); //イベントを発生させる
    }
}

p.315 超簡単1桁加算器 event02.cs(データ型の構造体とそのメンバ)

・C#の値型は構造体で定義されている(p.45)
・解釈としては、通常の型名を.NET型の型名(p.42)に内部的に変換し、この名の構造体として用いている
・例えば、int型はSystem.Int32型に変換され、System.Int32構造体として扱われる。
・よって、int.Parse()メソッド(p.45)、System.Int32構造体のメソッドであり、値型の各構造体にParse()メソッドが存在している。
・なお、データ型.MaxValue、データ型.MinValue(p.45)は構造体に含まれる定数。
・そして、event02.csで用いる Char.IsDigit(char)メソッドは、char型が変換されるSystem.Char構造体に含まれるメソッドであり、
 .NET型で表現すると Char.IsDigit(Char)メソッド となる。
・Char.GetNumericValue(char)メソッドも同様で、Char.GetNumericValue(Char)。
・なお、GetNumericValue(char)メソッドは、double.Parse("" + Char)で代用できるが、double.Parseは変換不能時に異常終了する。
 GetNumericValue(char)は-1.0を返す。

p.315 超簡単1桁加算器 event02.cs(ConsoleKeyInfo構造体とそのメンバ)

・System.ConsoleKeyInfoは、押されたキーの情報を返すためのデータ構造と、便利なプロパティを持つ構造体。
・Console.Readkey()メソッドの戻り値型がConsoleKeyInfo構造体で、Readkey()メソッドのbool型引数としてtrueを渡すとキー情報を
 コンソールに非表示にできる。
・p.293のConsole.KeyAvailableプロパティがキー入力があればtrueを返すので、この時にConsole.Readkey()メソッドを実行すると良い
・そして、押されたキーの情報は、ConsoleKeyInfo構造体のKeyCharプロパティで得られる。戻り値はchar型なので、
 文字キーであればchar型リテラルで比較可能。

p.315 event02.cs

//p.315 event02.cs
using System;
delegate void Handler(char ch); //デリゲートの宣言(戻り値無し、引数は文字)
class EventClass { //イベントの発生を担うクラス
    public event Handler KeyHit; //イベントフィールドをイベント名KeyHitで定義
    public void OnKeyHit(char ch) { //KeyHitイベントを発生させるメソッド
        if (KeyHit != null) { //イベントフィールドがnullでなければ
            KeyHit(ch); //イベントを発生させる
        }
    }
}
class Show { //イベントの発生により実行したいメソッドを持つクラス
    int sum = 0; //合計値
    public void keyshow(char ch) { //イベントの発生により実行したいメソッド
        if (Char.IsDigit(ch)) { //文字が10進数字(0~9)のキーが押されていたら
            int a = (int)char.GetNumericValue(ch); //文字を実数化、整数化する
            sum += a; //変換結果を合計値に足しこむ
            Console.WriteLine("+ {0}", a); //変換結果を表示
            Console.WriteLine("= {0}", sum); //合計値を表示
        } else if (ch == 'c') { //「c」キーが押されていたら
            sum = 0; //合計値をクリア
            Console.WriteLine("合計がクリアされました");
        } else { //数字キーでもcキーでもなければ
            return; //何もしない
        }
    }
}
class event02 { 
    public static void Main() {
        ConsoleKeyInfo cki; //キー情報のデータ構造などを持つ構造体
        EventClass ec = new EventClass(); //イベントの発生を担うクラス
        Show s = new Show(); //イベントの発生により実行したいメソッドを持つクラス
        ec.KeyHit += new Handler(s.keyshow); //イベントにデリゲートとメソッドを登録
        while (true) { //無限ループ
            if (Console.KeyAvailable) { //何かキーが押されていたら
                cki = Console.ReadKey(true); //そのキーを得る
                if (cki.KeyChar == 'x') { //xキーであれば
                    break; //ループを抜ける(終了)
                } else { //xキー以外であれば
                    ec.OnKeyHit(cki.KeyChar); //イベントを発生させる
                }
            }
        }
    }
}

アレンジ演習:p.315 event02.cs

・p.319にあるとおり、ラムダ式で書き換えよう

作成例

//アレンジ演習:p.315 event02.cs
using System;
delegate void Handler(char ch); //デリゲートの宣言(戻り値無し、引数は文字)
class EventClass { //イベントの発生を担うクラス
    public event Handler KeyHit; //イベントフィールドをイベント名KeyHitで定義
    public void OnKeyHit(char ch) { //KeyHitイベントを発生させるメソッド
        if (KeyHit != null) { //イベントフィールドがnullでなければ
            KeyHit(ch); //イベントを発生させる
        }
    }
}
class Show { //イベントの発生により実行したいメソッドを持つクラス
    int sum = 0; //合計値
    public void keyshow(char ch) { //イベントの発生により実行したいメソッド
        if (Char.IsDigit(ch)) { //文字が10進数字(0~9)のキーが押されていたら
            int a = (int)char.GetNumericValue(ch); //文字を実数化、整数化する
            sum += a; //変換結果を合計値に足しこむ
            Console.WriteLine("+ {0}", a); //変換結果を表示
            Console.WriteLine("= {0}", sum); //合計値を表示
        } else if (ch == 'c') { //「c」キーが押されていたら
            sum = 0; //合計値をクリア
            Console.WriteLine("合計がクリアされました");
        } else { //数字キーでもcキーでもなければ
            return; //何もしない
        }
    }
}
class event02 { 
    public static void Main() {
        ConsoleKeyInfo cki; //キー情報のデータ構造などを持つ構造体
        EventClass ec = new EventClass(); //イベントの発生を担うクラス
        Show s = new Show(); //イベントの発生により実行したいメソッドを持つクラス
        ec.KeyHit += (c) => s.keyshow(c); //【変更】イベントにデリゲートとメソッドを登録
        while (true) { //無限ループ
            if (Console.KeyAvailable) { //何かキーが押されていたら
                cki = Console.ReadKey(true); //そのキーを得る
                if (cki.KeyChar == 'x') { //xキーであれば
                    break; //ループを抜ける(終了)
                } else { //xキー以外であれば
                    ec.OnKeyHit(cki.KeyChar); //イベントを発生させる
                }
            }
        }
    }
}

p.320 練習問題 1 ex1201.cs

・p.308 lambda02.cs と同じ内容
・よって、アレンジ演習として、double型の2引数をより、それらの差を返すように書き換えよう
・なお、差は「大きい方 - 小さい方」で得ること

作成例

//アレンジ演習:p.320 練習問題 1 ex1201.cs
using System;
delegate double MyDelegate(double x, double y); //デリゲートの宣言(戻り値有り、2引数)
class lambda02 {
    public static void Main() {
        MyDelegate md = (x, y) => { return (x > y) ? x - y : y - x; }; //引数x、yの型は指定不要
        Console.WriteLine("2と3の差は{0}", md(2, 3)); //デリゲート経由で呼び出す
        Console.WriteLine("3.14と2.2の差は{0}", md(3.14, 2.2)); //デリゲート経由で呼び出す
    }
}

p.320 練習問題 2 ex1202.cs ヒント

・p.315 event02.csを基に下記のアレンジを行おう
① イベントを実行した結果をreturnできるように、イベントを発生させるメソッドの戻り値型をintに、引数型をint,intにする
② よって、イベントを発生させると共に、結果を返すように「return KeyHit(int,int);」とする
③ イベントフィールドがnullの時に何も返さないと文法エラーになるので「-1」を返すと良い
④ これに伴い、デリゲートの宣言を「戻り値あり(int)、引数あり(int,int)」にしよう
⑤ イベントの発生により実行したいことは「return x + y;」にできるので、Showクラスは不要になる
⑥ keyshow()メソッドで行っていた「文字が10進数字(0~9)のキーが押されていたら」「文字を実数化、整数化する」は、
 Main()メソッドで行うと良い
⑦ Main()メソッドでは、キー入力1回目の値を保持しておき、2回目のキー入力で2数を渡す形でイベントを発生させる
⑧ そして、結果を表示したら、ループを抜けて終了すると良い

作成例

//p.320 練習問題 2 ex1202.cs
using System;
delegate int Handler(int x, int y); //【変更】デリゲートの宣言(戻り値有り、引数は2整数)
class EventClass { //イベントの発生を担うクラス
    public event Handler KeyHit; //イベントフィールドをイベント名KeyHitで定義
    public int OnKeyHit(int x, int y) { //【変更】KeyHitイベントを発生させるメソッド
        if (KeyHit != null) { //イベントフィールドがnullでなければ
            return KeyHit(x, y); //【変更】イベントを発生させる
        } else { //【以下追加】nullであれば
            return -1; //ダミーの値として-1を返す
        }
    }
}
class event02 { 
    public static void Main() {
        ConsoleKeyInfo cki; //キー情報のデータ構造などを持つ構造体
        EventClass ec = new EventClass(); //イベントの発生を担うクラス
        //Show s = new Show(); //イベントの発生により実行したいメソッドを持つクラス
        ec.KeyHit += (x, y) => { return x + y; }; //【変更】イベントにデリゲートと処理を登録
        int left = -1; //【追加】キー入力1回目の値を保持する変数
        while (true) { //無限ループ
            if (Console.KeyAvailable) { //何かキーが押されていたら
                cki = Console.ReadKey(true); //そのキーを得る
                char ch = cki.KeyChar; //【追加】押されたキー名を取り出す
                if (Char.IsDigit(ch)) { //【移動】文字が10進数字(0~9)のキーが押されていたら
                    int a = (int)(char.GetNumericValue(ch)); //【移動】文字を実数化、整数化する
                    if (left == -1) { //【以下追加】キー入力1回目?
                        left = a; //入力値を保持
                    }
                    else { //キー入力2回目?
                        int sum = ec.OnKeyHit(left, a); //2入力値を渡してイベントを発生させる
                        Console.WriteLine("{0} + {1} = {2}", left, a, sum);
                        break; //無限ループを抜ける
                    }
                }
            }
        }
    }
}

第13章 例外

p.321 例外処理の基礎

・プログラムによって異常状態が発生すると「実行時エラー」として、発生時点で異常終了する。
・この異常状態を「例外(Exception)」として、これを表すクラスのオブジェクトで扱うことができる
・このオブジェクトを受け取って処理する仕組みが例外処理で、例外処理を組み込むことで「実行時エラー」をコントロールできる。
・例えば、p.322 exception01.cs のように、double.Parse()メソッドに実数に変換できない文字列を渡すと、
 Exceptionクラスの派生クラスであるFormatExceptionクラスのオブジェクトが生成され、例外処理がないので異常終了する。
・以下が異常終了時のメッセージとその解説:
 ハンドルされていない例外: System.FormatException: 入力文字列の形式が正しくありません。
 ⇒「ハンドルされていない」とは例外処理がないか例外処理対象になっていないことを示す
 ⇒「System.FormatException」は形式例外を表すクラス
 ⇒「入力文字列の形式が正しくありません。」はこのクラスのMessageプロパティにある文字列
   場所 System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt)
   場所 System.Double.Parse(String s)
   場所 MyClass.Main() 場所 F:\ha241_AkibaC#\Project1\Project1\exception01.cs:行 9
 ⇒下から上に向かって例外発生の経緯を示す。C#で実際にどう扱われたかがわかる
 ⇒一番下の1行で「発生の起因となったクラス名.メソッド名 場所:ソースファイル名:行番号」が明示されている

アレンジ演習:p.322 exception01.cs

・変数a、bをint型に変更しよう
・すると、System.FormatException(形式例外)以外の例外を試すことができる
 ①「割られる数」や「割る数」にint型の最大値を超える値や、最小値を下回る値を入力した場合
 ②「割る数」に 0 を入力した場合(double型の場合は「∞」と表示され異常終了しない)

作成例

//アレンジ演習:p.322 exception01.cs
using System;
class MyClass
{
    public static void Main()
    {
        Console.Write("割られる数--");
        string strA = Console.ReadLine();
        int a = int.Parse(strA); //【変更】FormatException、OverflowException発生可能性
        Console.Write("割る数---");
        string strB = Console.ReadLine();
        int b = int.Parse(strB); //【変更】FormatException、OverflowException発生可能性
        Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b); //DevideByZeroException発生可能性
    }
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です