講義メモ

・テキスト篇:p.320「練習問題」から
・ゲーム開発演習:文字の描画、画面遷移、画面再描画 など

p.320 練習問題 ヒント

・練習問題1は、p.308 lambda02.csと同じなので、さっそく練習問題2に進みましょう。
・練習問題2は、p.315 event02.csを基にしてアレンジしよう
・デリゲートの宣言を「戻り値有り、引数有り」にする
・その影響で、イベントを実行した結果をreturnする必要がある
・また、イベントフィールドがnullである場合にも何かをreturnする必要がある。今回は(入力を正の数のみとして)
 -1を返すとしよう
・event02と同じ仕掛けで2つの整数を順に受け取り、和を表示したら終了すれば良い(繰返す必要はない)
・イベントで呼ばれるクラスを作成しなくても良いのでシンプルにできる

p.320 練習問題2 ヒント:EventClassクラス

・「delegate void Handler(char ch); //デリゲートの宣言(戻り値なし、引数有り)」は
 「delegate int Handler(int x, int y); //デリゲートの宣言(戻り値有り、引数有り)」とする
・これにOnKeyHitメソッドを合わせるので「public int OnKeyHit(int x, int y) {」とする
・イベントを発生させる部分も合わせて「return KeyHit(x, y);」とする
・そして、イベントフィールドが無(準備中)である場合、-1を返すとするので
 「else { return -1; }」を加えよう

p.320 練習問題2 ヒント:ShowクラスとMainメソッド

・1つにまとめよう
・キー入力1回目の値を保持しておき、2回目のキー入力で2数を渡してイベントを発生させ、結果を表示するようにしよう

作成例

// p.320 練習問題2
using System;
delegate int Handler(int x, int y); //デリゲートの宣言(戻り値有り、引数有り)
class EventClass { //イベントの発生を担うクラスを定義
    public event Handler KeyHit; //イベントフィールドをイベント名KeyHitで定義
    public int OnKeyHit(int x, int y) { //KeyHitイベントを発生させるメソッド
        if (KeyHit != null) //イベントフィールドがnullでなければ
            return KeyHit(x, y); //イベントを発生させ結果を返す
        else
            return -1; //イベントフィールドがnullなら-1を返す
    }
}
class lambda02 {
    public static void Main() {
        ConsoleKeyInfo cki; //キーの情報を返すためのデータ構造をもつ構造体
        EventClass ec = new EventClass(); //イベントの発生を担うクラス
        ec.KeyHit += (x, y) => { return x + y; }; //イベントの発生により実行したいメソッドをデリゲートに追加
        int left = -1; //加算用の左辺値
        while (true) { //無限ループ
            if (Console.KeyAvailable) { //キー入力があった?
                cki = Console.ReadKey(false); //押されたキーを取得し表示する
                char ch = cki.KeyChar; //押されたキーの文字を取得
                if (Char.IsDigit(ch)) { //10進数字(0~9)キーか?
                    int a = (int)char.GetNumericValue(ch); //文字を実数に変換し整数にキャスト
                    if (left == -1) { //加算用の左辺値がまだならば
                        left = a; //入力値を左辺値にする
                        Console.WriteLine("に加えるのは?"); //表示
                    } else { //でなければ右辺値なので
                        int sum = ec.OnKeyHit(left, a); //イベントを発生させて和を得る
                        Console.WriteLine("なので\n{0} + {1} = {2}", left, a, sum); //合計を表示
                        break; //繰返しを抜ける
                    }
                }
            }
        }
    }
}

第13章 例外

p.321 例外処理の基礎

・プログラムにおいて異常事態が発生すると、通常、その時点で異常終了する
・プログラム言語によっては、異常事態を例外(Exception)としてプログラム側に通知してくれる
・よって、これを受け取って処理する仕組みを作り込んでおくことで、異常終了を避けられる
・この仕組みが例外処理
・例:double.Parse(文字列)メソッドに、実数変換できない文字列を渡すと、形式例外が発生し、対処を何もしないとその時点で異常終了する
・対処を組み込むには、C#の場合、try-catch構文と、Exceptionクラスの派生クラスを用いる

p.322 exception01.cs

//p.322 exception01.cs
using System;
class MyClass
{
    public static void Main()
    {
        Console.Write("割られる数--");
        string strA = Console.ReadLine();
        double a = double.Parse(strA); //①FormatException発生の可能性あり
        Console.Write("割る数---");
        string strB = Console.ReadLine();
        double b = double.Parse(strB); //②FormatException発生の可能性あり
        Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b);
    }
}

①でFormatException発生時のメッセージについて

・ハンドルされていない例外: System.FormatException: 入力文字列の形式が正しくありません。
 ⇒ 対処が記述されていないところで、形式例外が発生したことを示す
・場所 System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt)
 場所 System.Double.Parse(String s)
 場所 MyClass.Main() 場所 C:\Users\human\Documents\ha231\chap13\Project1\exception01.cs:行 9
 ⇒ 下から読むと、mainの9行目で例外が発生したことがわかる。その上2行は内部的に呼ばれたメソッド
 ⇒ 最上位にあるのが、呼ばれたメソッドで実際に例外が発生した場所を指す
・呼び出しスタックともいい、呼び出しの順に積み上げたイメージ
・後述のとおり、例外が発生すると呼び出し元に通知されることが繰返される

p.323(例外処理:try-catch構文)

・例外はどんなメソッドでも起こりえるので、対処すべき例外に絞り込む必要がある
・そして、その例外が発生しうる範囲を、tryブロックにする
・tryブロックに続けて、catchブロックを記述し、例外発生時の処置を記述する
・すると、catchブロックの最後の1行を終えた段階で異常終了せずに、ブロックの後続行から再開される
・なお、tryブロックに複数行ある&例外が途中で発生した場合、tryブロック内の後続の処理はスキップされて、catchブロックの1行目に進む
・また、この構文では、どんな例外が発生しても対処できてしまう(後述する対処法がある)

p.324 exception02.cs

//p.324 exception02.cs
using System;
class MyClass {
    public static void Main() {
        double a = 0.0, b = 0.0;
        Console.Write("割られる数--");
        string strA = Console.ReadLine();
        try { //例外処理対象①
            a = double.Parse(strA); //FormatException発生の可能性あり
        } catch { //対象①の例外発生時の処理
            Console.WriteLine("不適切な入力です"); 
        } //異常終了せずに続行する
        Console.Write("割る数---");
        string strB = Console.ReadLine();
        try { //例外処理対象②
            b = double.Parse(strB); //FormatException発生の可能性あり
        } catch { //対象②の例外発生時の処理
            Console.WriteLine("不適切な入力です");
        } //異常終了せずに続行する
        Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b);
    }
}

アレンジ演習:p.324 exception02.cs

・変数a、bをint型に変更しよう
・すると、入力値によって、形式例外に加えてオーバフロー例外とゼロ除算例外が発生するようになる
・形式例外に加えてオーバフロー例外のどちらの例外が発生しても「不適切な入力です」と表示して先に進んでしまうことを確認しよう
・なお、割る数の入力で例外が発生すると、0になるので、ゼロ除算例外が発生し異常終了する

作成例

//アレンジ演習:p.324 exception02.cs
using System;
class MyClass {
    public static void Main() {
        int a = 0, b = 0;
        Console.Write("割られる数--");
        string strA = Console.ReadLine();
        try { //例外処理対象①
            a = int.Parse(strA); //形式例外,オーバフロー例外発生の可能性あり
        } catch { //対象①の例外発生時の処理
            Console.WriteLine("不適切な入力です"); 
        } //異常終了せずに続行する
        Console.Write("割る数---");
        string strB = Console.ReadLine();
        try { //例外処理対象②
            b = int.Parse(strB); //形式例外,オーバフロー例外発生の可能性あり
        } catch { //対象②の例外発生時の処理
            Console.WriteLine("不適切な入力です");
        } //異常終了せずに続行する
        Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b); //ゼロ除算例外発生(異常終了)の可能性あり
    }
}

p.325(例外クラス)

・FormatExceptionはクラスであり、Exceptionクラスの派生クラス
・C#では例外をExceptionクラスの派生クラスによって表して、そのオブジェクトを生成して渡す(投げる)仕様になっている
・各例外クラスの中にも継承関係があり、ツリー状に構成されいている
・例:Exceptionの派生クラスがSystemExceptionで、その派生クラスがFormatException。
 また、FormatExceptionの派生クラスもある(FileFormatExceptionなど)
・よって、FormatExceptionを用いて、その派生クラスによる例外にも対処できる

p.325(例外処理:try-catch例外構文)

・catchにおいて例外クラスを指定することで、発生した例外毎に異なる対処をすることができる
・書式: try {例外処理対象} catch(例外クラス名 引数) {例外処理}
・書式: try {例外処理対象} catch(例外クラス名① 引数) {例外①処理} catch(例外クラス名② 引数) {例外②処理} …

p.326(例外クラスのメンバ)

・各例外クラスの基本クラスであるExceptionクラスには、例外処理に利用できるプロパティが含まれている
・string Messageプロパティ:例外メッセージ
・string Sourceプロパティ:例外発生プロジェクト名
・MethodBase TargetSiteプロパティ:例外発生メソッド情報(Console.Write等をするとメソッド名になる)
・string HelpLinkプロパティ:ヘルプファイルのURL
・string StackTraceプロパティ:例外発生メソッドからの呼び出し履歴
・また、Objectクラスのstring ToString()をオーバライドした「例外内容を示す文字列を返す」メソッドも提供されている
※ Objectクラスのstring ToString()はConsole.Write等をすると省略時に自動実行される

p.327 exception03.cs

//p.327 exception03.cs
using System;
class exception03 {
    public static void Main() {
        int[] arr = new int[5];
        try {
            arr[5] = 10; //IndexOutOfRangeException(添字範囲外)発生
        } catch (IndexOutOfRangeException io) { //↑に対する例外処理
            Console.WriteLine(io); //内部的にio.ToString()が自動的に動作
            Console.WriteLine("[io]---------");
            Console.WriteLine(io.Source); //例外発生プロジェクト名
            Console.WriteLine("[io.Source]---------");
            Console.WriteLine(io.Message); //例外メッセージ
            Console.WriteLine("[io.Message]---------");
            Console.WriteLine(io.ToString()); //例外内容を示す文字列
            Console.WriteLine("[io.ToString()]---------");
            Console.WriteLine(io.TargetSite); //例外発生メソッド情報
            Console.WriteLine("[io.TargetSite]---------");
        }
    }
}

p.326 System.Objectクラス

・全クラスの暗黙の基本クラスとして提供されており、プログラマがクラスを定義しても、自動的に、System.Objectクラスの派生クラスになる
・自動的なので、継承を明示する必要はない
・なお、C#の通常の型指定では「object」で.NET型では「System.Object」となるので同じものを指す
・このクラスのもつ特殊なメソッドがstring ToString()で「現在のオブジェクトを表す文字列を返す」と定義されている
・C#が提供するクラスでは、基本的にToString()をオーバライドして、そのクラスを表す文字列を返してくれる
・プログラマが定義するクラスにおいても、共有する場合は、ToString()をオーバライドして、そのクラスを表す文字列を定義することが
 推奨される
・なお、オブジェクトの参照変数をConsole.Write等するとToString()が自動実行される

p.329(catchの複数記述)

・catchにおいて例外クラスを指定することで、発生した例外毎に異なる対処をすることができる
・書式: 
 try {例外処理対象} 
 catch(例外クラス名① 引数) {例外①処理} 
 catch(例外クラス名② 引数) {例外②処理} 
 :
・ただし、例外処理対象で例外が発生すると、記述の上から順にマッチングされるので、対応する例外クラスの基本クラスが先に記述されていると
 「絶対に実行されない文が出来るのでエラー」となるので注意

提出:アレンジ演習:p.324 exception02.cs

コメントを残す

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