講義メモ

テキスト篇:p.329「exception04.cs」から
ゲーム開発演習:文字の描画、画面遷移、画面再描画 など

p.329(catchの複数記述):再掲載+α

・catchにおいて例外クラスを指定することで、発生した例外毎に異なる対処をすることができる
・書式: 
 try {例外処理対象} 
 catch(例外クラス名① 引数) {例外①処理} 
 catch(例外クラス名② 引数) {例外②処理} 
 :
・ただし、例外処理対象で例外が発生すると、記述の上から順にマッチングされるので、対応する例外クラスの基本クラスが先に記述されていると
 「絶対に実行されない文が出来るのでエラー」となるので注意
・最後に「catch (Exception){…}」を記述することで「上記のどれにも当てはまない例外が発生したら」という記述ができる
・if文のelseやswitch文のdefaultに近い位置づけとなる
・なお、この記述により「どんな例外が起きても異常終了しない(p.323の構文)」となるので注意

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 (Exception ex) { //汎用例外処理(実行されない):if文のelseやswitch文のdefaultに近い位置づけ
            Console.WriteLine(ex.Message);
        }
    }
}

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

・p.330上のように、catchブロックの順序を変更するとエラーになることを確認しよう
・基本の例外クラスのcatchにより、派生の例外クラスで示す例外を処理できる
・IndexOutOfRangeExceptionクラスとDivideByZeroExceptionクラスはどちらも、Exceptionクラスの派生クラスだが、
 この2クラスの間には継承関係はないので、catchブロックの順序を変更しても良い
・しかし、catch (Exception ex)を、catch (IndexOutOfRangeException io)やcatch (DivideByZeroException)の前に置くと
 「絶対に実行されない文が出来るのでエラー」となる

作成例(実行はできない)

//アレンジ演習: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 (DivideByZeroException) { //ゼロ除算例外処理(実行される)
            Console.WriteLine("0で割っちゃだめだよ");
        }
        catch (Exception ex) { //汎用例外処理(実行されない):if文のelseやswitch文のdefaultに近い位置づけ
            Console.WriteLine(ex.Message);
        }
        catch (IndexOutOfRangeException io) { //配列範囲外例外処理(実行されない) 【エラーになる】
            Console.WriteLine(io.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.331 exception05.cs

・「例外が発生しcatchされなかった場合」を確認しよう
・その為に「catch (Exception e)」を「catch (FormatException e)」に書き換えよう
・そして、int型の最大値を超える入力を行って、finallyブロックの内容が実行された後、異常終了することを確認しよう

作成例

//アレンジ演習: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 (FormatException e) { //【変更】形式例外処理
                Console.WriteLine(e.Message);
            }
            finally { //例外発生の有無にかかわらず実行する処理
                Console.Write("続けますか(Y/N)---");
                if (Console.ReadLine()[0] == 'N') {
                    bEnd = true; //終了フラグをオンに
                }
            }
            if (bEnd) { //終了フラグがオン?
                break; //繰返しを抜ける
            }
        }
    }
}

p.333(例外のコールスタック)

・p.326で例外クラスのStackTraceプロパティでコールスタックとある通り、メソッドからメソッドを呼ぶ構造はスタックで表現されている
・そして、呼ばれているメソッドで例外が発生すると、即時に異常終了することはなく、呼んでいるメソッドの呼び出した位置で
 例外が発生したことになる
・よって、そこに例外処理を記述して対応できるし、あえて対応しないことで、そのメソッドを呼び出している
 さらに上位のメソッドに任せることもできる
例:
void Method1() { 
 Method2(); //①メソッド2を呼ぶ、⑥ゼロ除算例外が戻されるが例外処理がないので異常終了
}
void Method2() { 
 Method3(); //②メソッド3を呼ぶ、⑤ゼロ除算例外が戻されるが例外処理がないのでMethod1に投げる
}
void Method3() { 
 int a = 0; b = 1 / a; //③ゼロ除算例外発生、④例外処理がないのでMethod2に戻す(投げる)
}

p.333 throw文

・プログラマが意図的に例外を発生させる(例外を投げる)ために用いるのがthrow文
・書式: throw 例外オブジェクト;
・例外オブジェクトは、catchブロックで受け取ったものでも良く「new 例外クラス()」で生成しても良い
・なお、catchブロックの中で受け取った例外オブジェクトをthrowすることで、例外処理を行ってから、
 同じ例外を理由として異常終了させる(または呼び出し側に任せる)ことができる
・書式例:
 try {…}
 catch (例外クラス 仮引数) { 例外処理; throw 仮引数; }
例:
void Method1() { 
 try {
  Method2(); //①メソッド2を呼ぶ、⑥例外が戻されるので例外処理へ
 } catch {
  例外処理; //⑦Method2やMethod3における例外をまとめて処理できる
 }
}
void Method2() { 
 Method3(); //②メソッド3を呼ぶ、例外が戻されるが例外処理がないのでMethod1に投げる
}
void Method3() { 
 throw new Exception(); //③例外を発生させる、④例外処理がないのでMethod2に戻す(投げる)
}
・特に、他のクラスにあるメソッドや、共通で用いられるメソッドの場合、呼ばれる側で例外が発生した場合に、
 どうすれば良いか決められないことが多い
・よって、例外処理は呼び出しの大本で行うべきであり(例:画面に表示、通信で通知など)呼ばれる側に例外処理を書く必要はないことが多い
・また、この仕掛けを利用することで、呼び出し側に事態を知らせることにも用いることができる
例:
void Method1() { 
 try {
  Method2(●); //①メソッド2を呼ぶ、③例外が戻されるので例外処理へ
 } catch {
  例外処理; //④Method2における例外を処理できる
 }
}
void Method2(●) { 
 if (● < 0) throw new Exception(); //②負の数なら例外を起こす、例外処理がないのでMethod1に投げる
}

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.334 exception06.cs

・部品的なメソッド①の例外処理では、i.Messageの表示のみを行い、起こった例外を投げるだけにしよう
・部品的なメソッド②の例外処理では、本来の配列範囲外例外の対処を行うようにしよう

作成例

//アレンジ演習: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 i; //【変更】引数で受け取った例外オブジェクトを呼び出し元に投げる
        }
    }
}
class MyClassB { //部品的なクラス②
    public void Calc() { //部品的なメソッド②
        MyClassA a = new MyClassA(); //部品的なクラス①のインスタンス生成
        try { //例外処理対象
            a.Calc(); //部品的なメソッド①を呼ぶ(ゼロ除算例外オブジェクトが投げられてくる)
        }
        catch (IndexOutOfRangeException 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文が記述可能で、catchした例外がそのまま投げられる
・例外処理を行ってから、同じ例外を理由として異常終了させたい場合や、呼び出し側に任せたい場合に用いる

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); //例外を投げたメソッド名「Void Calc()」を表示
        }
    }
}

p.337 tryのネスト

・tryブロックの中に、さらにtry-catch構造を記述できる
・これにより、複雑な例外処理が必要だったり、例外処理内容が重複する場合の記述をシンプルにできる

p.337 正誤:exception08.cs

・013行目の「"{0},{1}"」は「"{0}"」の誤り(コンパイルエラーにはならないが)

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}", x / y); //ゼロ除算例外発生
            }
            catch (IndexOutOfRangeException i) { //例外処理対象②の配列範囲外例外処理(実行されない)
                Console.WriteLine(i.Message);
            }
        }
        catch (DivideByZeroException d) { //例外処理対象①のゼロ除算例外処理
            Console.WriteLine(d.Message);
        }
    }
}

アレンジ演習:p.337 exception08.cs

・例外処理対象②でゼロ除算例外ではなく、配列範囲外例外を発生させて、動作がどう変わるか確認しよう

作成例

//アレンジ演習:p.337 exception08.cs
using System;
class exception08 {
    public static void Main() {
        int x = 10, y = 0;
        try { //例外処理対象①
            try { //例外処理対象②
                int[] a = {0}; //【追加】
                Console.WriteLine("{0}", a[1]); //【変更】配列範囲外例外発生
            }
            catch (IndexOutOfRangeException i) { //例外処理対象②の配列範囲外例外処理
                Console.WriteLine(i.Message);
            }
        }
        catch (DivideByZeroException d) { //例外処理対象①のゼロ除算例外処理(実行されない)
            Console.WriteLine(d.Message);
        }
    }
}

p.338 独自の例外を作る

・全例外クラスの基本クラスであるExceptionクラスは、プログラマが継承するクラスを記述できる
・また、(一部を除き)各例外クラスについても、プログラマが継承するクラスを記述できる
 ※ 継承不可: IndexOutOfRangeExceptionクラス等
・よって、プログラマが必要に応じて、意味の近い例外クラスを継承するクラスを記述して用いると良い
・この時、p.326で説明したToString()メソッドをオーバーライドし、Messageプロパティをnew指定で隠ぺいすることで、
 新たなクラスの説明文を表示するようにすると良い
・exception09.csのように、既存の例外クラスのプロパティやメソッドの内容を書き換えた自前の例外を作りたい場合にも利用できる

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);
        }
    }
}

コメントを残す

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