テキスト編: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発生可能性
}
}