講義メモ

テキスト編:p.327「exception03.cs」から
ゲーム開発演習:敵機の出現、敵機の移動、衝突判定
p.326 例外クラスの主なメンバ(再掲載)

・catchブロックに引数として例外クラスと引数名を指定することで得られる例外オブジェクトが持つ情報は、
 例外クラスに共通なメンバ(メソッドとプロパティ)を用いて得られる
・詳細は公式ドキュメントで閲覧可能
 https://learn.microsoft.com/ja-jp/dotnet/api/system.exception?view=net-8.0
・なお、テキストに記載のToString()メソッドは、例外クラスに限ったものではなく、すべてのクラスにおいて、
 その情報を返すメソッドとして記述する(オーバーライドする)ことが推奨されている。

p.326 System.Objectクラス(再掲載)

・Object型はInt32などと同様の.NETフレームワーク型であり、.NETフレームワーク型にはすべて対応するクラスまたは構造体が提供されている
・int型などと同様に、object型を用いると、.NETフレームワークのObject型として扱われる
・object/Object型に対応するクラスがSystem.Objectクラスで、全てのクラスの暗黙の基本クラス(ルートクラス)として定義されており、
 継承を指定しなくても、自動的に用いられる
・このSystem.Objectクラスの持っているメンバの一つがToString()メソッドなので、すべてのクラスにおいてToString()メソッドの
 オーバーライドが可能。
・このことを利用して、オブジェクト名(参照変数)をConsole.Writeなどに指定すると、自動的に「参照変数.ToString()」が
 実行されるようになっている。
例: p.327 exception03.csで「Console.Write(io)」と「Console.Write(io.ToString())」を実行しているが
 同じ結果になる

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; //配列範囲外例外が発生
        } 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.328(catchブロックの書式)

・p.324の通り「catch{…}」のみで引数のない形式があり、全例外を無条件で捕捉する
・「catch(例外クラス名 引数){…}」とすることで、捕捉する例外(例外の基本クラス)を指定し、そのメンバを引数経由で利用できる
・メンバを用いる必要がなければ「catch(例外クラス名){…}」と引数を省略することも可能
・「if~else if」構造と同様に、1つのtryに対して複数のcatchを記述できる
・ただし、「if~else if」構造と同様に、上から順に評価されることに注意
・継承関係のある例外クラスを指定する場合は、派生クラスを上にしないと、基本クラスでcatchしてしまうことになり
「絶対に実行されないソース」となるためエラーになる。

p.329 exception04.cs

//p.329 exception04.cs
using System;
class exception04 {
    public static void Main() {
        int x = 10, y = 0;
        try { //例外処理範囲
            Console.WriteLine("{0} / {1} = {2}", x, y, x / y); //ゼロ除算例外発生
        } catch (IndexOutOfRangeException io) { //配列範囲外例外処理(実行対象外)
            Console.WriteLine(io.Message);
        } catch (DivideByZeroException) { //ゼロ除算例外処理
            Console.WriteLine("0で割っちゃだめだよ"); //これ以降のcatchは無効
        } catch (Exception ex) { //例外処理(無視)
            Console.WriteLine(ex.Message);
        }
    }
}

アレンジ演習:p.329 exception04.cs

・p.330のように書き換えるとエラーになることを確認しよう

作成例

//p.329 exception04.cs
using System;
class exception04 {
    public static void Main() {
        int x = 10, y = 0;
        try { //例外処理範囲
            Console.WriteLine("{0} / {1} = {2}", x, y, x / y); //ゼロ除算例外発生
        } catch (Exception ex) { //例外処理(無視)
            Console.WriteLine(ex.Message);
        } catch (IndexOutOfRangeException io) { //配列範囲外例外処理←エラー
            Console.WriteLine(io.Message);
        } catch (DivideByZeroException) { //ゼロ除算例外処理←エラー
            Console.WriteLine("0で割っちゃだめだよ"); 
        }
    }
}

アレンジ演習:p.329 exception04.cs

・基に戻してから、tryブロックの中を工夫して、3つの例外処理を全て試せるようにしよう
・コンソールから入力した値によって、配列範囲外例外、ゼロ除算例外、オーバフロー例外が起こるようにしよう
・オーバフロー例外はExceptionクラスで捕捉すれば良い

作成例

//アレンジ演習:p.329 exception04.cs
using System;
class exception04 {
    public static void Main() {
        int[] x = new int[2];
        try { //例外処理範囲
            Console.Write("添字:"); 
            int i = int.Parse(Console.ReadLine()); //オーバーフロー、形式例外発生可能性
            Console.Write("値:"); 
            x[i] = int.Parse(Console.ReadLine()); //配列範囲外、オーバーフロー、形式例外発生可能性
            Console.WriteLine("10 / {0} = {1}", x[i], 10 / x[i]); //ゼロ除算例外発生可能性
        } catch (IndexOutOfRangeException io) { //配列範囲外例外処理
            Console.WriteLine(io.Message);
        } catch (DivideByZeroException) { //ゼロ除算例外処理
            Console.WriteLine("0で割っちゃだめだよ");
        } catch (Exception ex) { //例外処理(オーバーフロー、形式例外はここで)
            Console.WriteLine("その他の例外:" + ex.Message);
        }
    }
}

p.330 finallyブロック

・例外処理において例外発生の有無にかかわらず実行したい処理がある場合、finallyブロックを用いて記述する
・利用例としては、データベースや通信などの外部リソースからの切断や、実行者への通知などのような後始末が多い。
・例外が発生しなければ、tryブロックの末尾の次にfinallyブロックの内容が実行され処理続行
・例外が発生しcatchされた場合は、catchブロックの末尾の次にfinallyブロックの内容が実行され処理続行
・例外が発生しcatchされなかった場合は、finallyブロックの内容が実行され、その末尾の後で異常終了

p.331 exception05.cs

//p.331 exception05.cs
using System;
class exception05 {
    public static void Main() {
        string strWarusu; //入力用
        int x; //変換結果
        bool bEnd = false; //終了フラグ(初期値:オフ)
        while (true) { //無限ループ
            Console.Write("割る数--- ");
            strWarusu = Console.ReadLine();
            try { //例外処理対象
                x = int.Parse(strWarusu); //形式例外、オーバーフロー例外発生可能性
                Console.WriteLine("10 / {0} = {1}", x, 10 / x); //ゼロ除算例外可能性
            } catch (DivideByZeroException d) { //ゼロ除算例外処理
                Console.WriteLine(d.Message);
            } catch (Exception e) { //その他の例外処理
                Console.WriteLine(e.Message);
            } finally { //例外の有無にかかわらず実行すること
                Console.Write("続けますか(Y/N)---");
                if (Console.ReadLine()[0] == 'N') {
                    bEnd = true; //終了フラグをオンに
                }
            }
            if (bEnd) { //終了フラグがオン?
                break; //繰返し終了=処理終了
            }
        }
    }
}

p.333 throw文

・プログラマが自ら例外を投げることができる
・例えば、例外と同様に扱いたいような状態(例:身長が負の数)の時に便利
・また、catchブロックの中で例外処理を呼び出し元に依頼したい場合にも有効
※ 通常、呼ばれているメソッドの中の例外処理では、対処が決められない場合や、対処が重複してしまうことが多いので、
 呼んでいる側に任せる方が良い
・書式: throw 例外オブジェクト;
※ catchの引数で受け取った例外オブジェクトを用いることができるが、必要であれば、下記の書式で例外オブジェクトを生成して投げると良い
 throw new 例外クラス();

p.334 exception06.cs

//p.334 exception06.cs
using System;
class MyClassA {
    public void Calc() {
        int x = 10, y = 0;
        int[] arr = new int[5] { 1, 2, 3, 4, 5 };
        try { //例外処理範囲
            Console.WriteLine("{0}, {1}", arr[x], x / y); //配列範囲外例外発生
        } catch (IndexOutOfRangeException i) { //配列範囲外例外処理
            Console.WriteLine(i.Message);
            DivideByZeroException d = new DivideByZeroException(); //ゼロ除算例外オブジェクト生成
            Console.WriteLine("外側にthrowします");
            throw d; //ゼロ除算例外オブジェクトを投げる
        }
    }
}
class MyClassB {
    public void Calc() {
        MyClassA a = new MyClassA();
        try { //例外処理範囲
            a.Calc(); //ゼロ除算例外発生(投げられてくる)
        } catch (DivideByZeroException d) { //ゼロ除算例外処理
            Console.WriteLine("外側のcatch節です");
            Console.WriteLine(d.Message);
        }
    }
}
class exception06 {
    public static void Main() {
        MyClassB b = new MyClassB();
        b.Calc(); //結果的に例外が捕捉されるので正常終了する
    }
}

p.336(単独のthrow文)

・catchブロックの中で「throw;」と単独で実行すると、発生中の例外のオブジェクトを投げる

p.336 exception07.cs

//p.336 exception07.cs
using System;
class MyClass {
    int x = 5, y = 0;
    public void Calc() {
        try { //例外処理対象
            Console.WriteLine("{0}", x / y); //ゼロ除算例外発生
        } catch (DivideByZeroException d) { //ゼロ除算例外処理
            Console.WriteLine(d.Message);
            throw; //呼出し元へゼロ除算例外を投げる
        }
    }
}
class MyClassB {
    public static void Main() {
        MyClass mc = new MyClass();
        try { //例外処理対象
            mc.Calc(); //ゼロ除算例外発生(投げられてくる)
        } catch (DivideByZeroException d) { //ゼロ除算例外処理
            Console.WriteLine(d.TargetSite); //メソッド名を表示
        }
    }
}

p.337 tryのネスト

・tryブロックの中に、さらにtry-catch構造を記述できる
・これにより、複数のtry-catch構造で重複している記述があれば、外側のtryブロックのcatchでまとめて扱うこと可能

p.337 exception08.cs

//p.337 exception08.cs
using System;
class exception08 {
    public static void Main() {
        int x = 10, y = 0;
        try { //外側の例外処理対象(この中に複数のゼロ除算例外を投げる処理を記述可)
            try { //内側の例外処理対象
                Console.WriteLine("{0},{1}", x / y); //ゼロ除算例外発生
            } catch (IndexOutOfRangeException i) { //配列範囲外例外処理(対象外)
                Console.WriteLine(i.Message);
            } //ここで例外発生状態のままになる
        } catch (DivideByZeroException d) { //ゼロ除算例外処理(対象)
            Console.WriteLine(d.Message); //実行される
        }
    }
}

p.338 独自の例外を作る

・全ての例外クラスの基本クラスであるExcptionクラスは、プログラマが継承することを許可しており、
 自前の派生クラスもまた例外処理に利用可能
・加えて、一部を除く、既存の派生例外クラスも継承が可能
・よって、Excptionクラスまたは、特性の近い派生例外クラスを継承して、独自の例外を作ると良い
 例: 形式が定めてある入力でルール不一致の入力があれば形式例外の派生クラスのオブジェクトを投げると良い
・独自の例外においては、ToString()メソッドやMessageプロパティをオーバライドして、説明文を持たせると良い
(必須の場合もある)

p.339 exception09.cs

//p.339 exception09.cs
using System;
class MyEx : DivideByZeroException { //ゼロ除算例外クラスを継承した自前例外クラス
    public new string Message = "0で割るエラーです"; //メッセージを上書き
    public new string HelpLink = "http://www.kumei.ne.jp/c_lang/"; //リンクを上書き
    public override string ToString() { //メソッドをオーバーライド
        return "0で割ってはいけません!!";
    }
}
class exception09 {
    public static void Main() {
        int x;
        Console.Write("割る数(整数)--- ");
        string strWaru = Console.ReadLine();
        try { //例外処理範囲
            x = int.Parse(strWaru); //形式例外、オーバーフロー例外の可能性
            if (x == 0) {
                throw new MyEx(); //自前の例外を投げる
            }
            Console.WriteLine("12 / {0} = {1}", x, 12 / x); //ゼロ除算例外は発生しない
        } catch (MyEx me) { //自前の例外の処理
            Console.WriteLine(me.ToString()); //オーバーライドメソッドを呼ぶ
            Console.WriteLine(me.Message); //上書きしたメッセージ
            Console.WriteLine(me.HelpLink + "を参照"); //上書きしたリンク
        } catch (Exception e) { //形式例外、オーバーフロー例外処理
            Console.WriteLine(e.Message);
        }
    }
}

p.341 checkedとunchecked

・int型などの数値型変数への代入の時点で、その型の範囲を超えているとオーバーフロー例外が発生する
・しかし、計算途中における型の範囲の超過では発生せず、誤った結果になる
・これを阻止したい場合は、checkedブロックの中に置けばよい
・よって、例外処理も必要であれば、tryブロックの中にcheckedブロックを記述すると良い
・テキストでは「checked()」形式も紹介されているが、推奨されない場合がある
・なお、checkedブロックの中の一部分について、その効果を無効化したい場合は、その範囲をuncheckedブロックにすればよい

p.343 checked02.cs

//p.343 checked02.cs
using System;
class checked02 {
    public static void Main() {
        int x, y, z;
        try { //例外処理対象
            checked { //計算途中のオーバーフローを例外とする範囲
                x = int.MaxValue;
                y = 1;
                z = x + y; //計算途中のオーバーフロー発生⇒例外を投げる
                Console.WriteLine(z); //実行されない
            }
        } catch (OverflowException o) { //オーバーフロー例外処理
            Console.WriteLine(o.Message);
            Console.WriteLine(o.StackTrace);
        }
    }
}

p.344 練習問題 ヒント

・「forループで」とあるが、終了条件の指定がないので「for (byte b = 1; ; b++)」という無限ループになる
・動作がわかるように、for文の中で値を表示すると良い
・「catch」でオーバーフロー例外の処理を記述し、発生した旨を表示したら、breakで無限ループを抜けること

作成例

//p.344 練習問題
using System;
class ex13 {
    public static void Main() {
        byte x = 1;
        for (byte b = 1; ; b++) { //無限ループ
            try { //例外処理対象
                checked { //計算途中のオーバーフローを例外とする範囲
                    x *= b; ////計算途中のオーバーフロー発生⇒例外を投げる
                    Console.WriteLine("途中:" + x);
                }
            } catch (OverflowException o) { //オーバーフロー例外処理
                Console.WriteLine(o.Message);
                break;
            }
        }
        Console.WriteLine("最終:" + x);
    }
}

コメントを残す

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