テキスト編:p.323「例外処理の基礎(tryブロック・catchブロック)」から
ゲーム開発演習:キーボードの状態を得る、画像の左右移動、簡易アニメーション など
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.323 例外処理の基礎(続き)try-catch
・例外処理対象はtryブロックの記述によって指定する
・tryブロック内で例外が発生したら、その直後にcatchブロックを記述することで、対処ができる
・catchブロックで対処が行われたら例外状態は解消され、処理が続行される
・ただし、tryブロック内に複数の記述があり、その途中で例外が発生した場合は、発生行の下の行は実行されない
・例:下記の場合、①④⑤の順に実行され、②③はスキップされる
try {
①例外が発生する処理
②処理
③例外が発生する処理
} catch {
④対処の処理
}
⑤通常処理
アレンジ演習:p.324 exception02.cs
・元のプログラムでは実数除算なのでゼロ除算の例外が発生しないので、形式例外(FormatException)のみになる ・そこで、変数a、bをint型にして、ゼロ除算の例外処理も記述しよう ・なお、実数除算で0.0以外の正の値を0.0で割ると∞、0.0以外の負の値を0.0で割ると-∞,0.0を0.0で割ると NaN(Not a Number=非数)となる。
作成例
//アレンジ演習: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("不適切な入力です"); //形式例外発生時の処理
}
try { //【以下追加】例外処理対象範囲③
Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b); //ゼロ除算例外発生の可能性
}
catch {
Console.WriteLine("除算はできません"); //ゼロ除算発生時の処理
}
}
}
作成例
//アレンジ演習:p.327 exception03.cs
using System;
class exception03 {
public static void Main() {
int[] arr = new int[5];
try { //例外処理範囲
//下2行でオーバーフロー例外、形式例外発生の可能性
Console.Write("添字:"); int a = int.Parse(Console.ReadLine()); //【追加】
Console.Write("代入値:"); int b = int.Parse(Console.ReadLine()); //【追加】
arr[a] = b; //【変更】添字範囲外例外発生の可能性
}
catch (IndexOutOfRangeException io) { //添字範囲外例外処理
Console.WriteLine(io);
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の複数指定
・try-catch構文は、if-elseif構文に近い位置づけでcatchを複数指定できる
・if-elseif構文と同様に、最初に行われたcatchのみが実行され、それ以外のcatchは実行されない
・この時、例外クラスの継承関係において、上位のクラスのcatchが前方にあると、後方のcatchが文法エラーになるので注意
・なお、例外クラスの継承関係において、上位のクラスのcatchを記述することで、この派生クラスの例外処理をまとめて行うことができる
※ チームルールにより、catch{…}が非推奨の場合、catch(Exception)、catch(SystemException)も非推奨とすることが多い
アレンジ演習:p.329 excepition04.cs
・このプログラムではDivideByZeroExceptionしか発生しないので、例外処理対象を書き換えよう ・そして、IndexOutOfRangeException、DivideByZeroException、これ以外の例外(例:FormatException)の3種が コンソールからの入力値によって発生するようにしよう
作成例
//アレンジ演習:p.329 excepition04.cs
using System;
class exception04 {
public static void Main() {
int x = 10, y = 0;
try {
//下2行でオーバーフロー例外、形式例外発生の可能性
Console.Write("x:"); x = int.Parse(Console.ReadLine()); //【追加】
Console.Write("y:"); y = int.Parse(Console.ReadLine()); //【追加】
Console.WriteLine("{0} / {1} = {2}", x, y, x / y); //ゼロ除算例外発生の可能性
int[] a = new int[2]; //【追加】
a[x] = y; //【追加】添字範囲外例外発生の可能性
Console.WriteLine("a[{0}] = {1}", x, y); //【追加】
}
catch (IndexOutOfRangeException io) { //添字範囲外例外処理
Console.WriteLine(io.Message);
}
catch (DivideByZeroException) { //ゼロ除算例外処理
Console.WriteLine("0で割っちゃだめだよ");
}
catch (Exception ex) { //上記以外の例外処理
Console.WriteLine("その他の例外:" + ex.Message); //【変更】
}
}
}
p.330 finallyブロック
・例外処理において、例外発生の有無にかかわらず実行したい処理がある場合、finallyブロックに記述できる
・例えば、データベースや通信などの外部リソースからの切断のような後始末を確実に行う場合に用いる
・書式例: try { ①例外処理対象 } catch(例外名) { ②例外処理 } finally { ③後始末処理 }
・動作は下記のように分岐する
・①で例外が発生しなければ、③を実行し、処理は続行
・①で例外が発生し、catchされたら、②を実行し、③を実行し、処理は続行
・①で例外が発生し、catchされなかったら、③を実行してから、異常終了
※後始末の処理内容によっては、③の中にさらに例外処理を記述する場合がある
アレンジ演習:p.331 exception05.cs
・例外が発生してcatchされなかった場合を試すために「catch (Exception e)」を「catch (FormatException e)」に変更しよう ・すると、整数に変換できない文字列の入力時の例外処理になり、オーバーフロー例外発生時にはcatchされなくなる ・よって「2200000000」等を入力すると、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') { //入力文字列の先頭文字が'N'?
bEnd = true; //終了フラグをオンにする
}
} //※例外がcatchされていないときはここで異常終了
if (bEnd) { //終了フラグがオン?※例外がcatchされていないときは実行されない
break; //ループを抜ける
}
}
}
}
p.333 throw文(例外のコールスタック)
・p.326のStackTraceプロパティの「意味」にある「コールスタック」は呼び出しの階層のことで、メソッドが他のメソッドを呼びだし、
そのメソッドがさらに他のメソッドを呼ぶ場合、呼び出した順に戻ってくること。
・例:下記の場合、処理①、処理③、処理⑤、処理④、処理②の順に実行される
public static void Main() { 処理①; f1(); 処理②; }
void f1() { 処理③; f2(); 処理④; }
void f2() { 処理⑤; }
・なお、呼び出されたメソッドで例外が発生した場合、そこでは異常終了せず、呼び出し元に戻戻っていく
・そして、最終的に例外処理が見つからなければ、もっとも上位の呼び出しにおいて異常終了する
・例:下記の場合、処理①、処理③、処理⑤の順に実行され、f2に例外処理がないので、f1に戻され、f1に例外処理がないのでMainに戻され、
Mainにもないので異常終了する
public static void Main() { 処理①; f1(); 処理②; }
void f1() { 処理③; f2(); 処理④; }
void f2() { 処理⑤で例外発生; }
・この仕組みを例外のコールスタックといい、呼び出し先での例外を呼び出し元でcatchできるので部品化に向く
・例えば、汎用的な処理を記述した部品はUI(ユーザインタフェース)を持たないので、UIを持つ処理から呼び出して、
例外処理はUIで行えば良い
p.333 throw文
・プログラマが意図的に例外を発生させる=例外を投げるのがthrow文
・書式: throw 例外オブジェクト;
・例外オブジェクトは「new Exceptionクラスか派生クラス()」で生成できる
・あるいは、catch (例外クラス 引数){…} の引数を用いることもできる
・このことを利用して、呼び出されたメソッドの中で例外処理を行い(正常終了させずに)呼び出し元に例外を投げて、
対処を任せることが多い
・なお、呼び出されたメソッドの中で例外処理を行うと、正常状態で呼び出し元に戻るので、不都合が発生する場合があるので注意
・例:下記の場合、処理①、処理③、処理⑤の順に実行され、例外が発生すると例外処理されてしまい、④②が実行される
public static void Main() { 処理①; f1(); 処理②; }
void f1() { 処理③; f2(); 処理④; }
void f2() { try { 処理⑤; } catch(Exception e) {} } //throw無し
・例:下記の場合、処理①、処理③、処理⑤の順に実行され、例外が発生すると、f2に例外処理がないので、f1に戻され、
f1に例外処理がないのでMainに戻され、Mainにもないので異常終了する
public static void Main() { 処理①; f1(); 処理②; }
void f1() { 処理③; f2(); 処理④; }
void f2() { try { 処理⑤; } catch(Exception e) {throw e;} } //throw有
p.334 exception06.cs
using System;
class MyClassA {
public void Calc() { //④かr呼ばれる
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
・このプログラムでは添字範囲外例外が発生しているのに、プログラマがゼロ除算例外に書き換えてしまっており、 本来は避けるべき作りになっている ・正しい対処を行うように書き換えよう ・変数x、yをコンソールから与えることで、添字範囲外例外とゼロ除算例外の発生を切り替えると良い
作成例
//アレンジ演習:p.334 exception06.cs
using System;
class MyClassA {
public void Calc() { //④かr呼ばれる
int x = 10, y = 0; //⑤
int[] arr = new int[5]{1, 2, 3, 4, 5}; //⑥
try { //例外処理範囲
//下2行でオーバーフロー例外、形式例外発生の可能性
Console.Write("x:"); x = int.Parse(Console.ReadLine()); //【追加】
Console.Write("y:"); y = int.Parse(Console.ReadLine()); //【追加】
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;」のみで良い
アレンジ演習:p.336 exception07.cs
・変数x、yをコンソールから与えることで、ゼロ除算例外の以外の例外発生時の処理を確認しよう ⇒ ゼロ除算例外のみがthrowの対象になる
作成例
//アレンジ演習:p.336 exception07.cs
using System;
class MyClass {
int x = 5, y = 0;
public void Calc() {
try { //例外処理範囲
//下2行でオーバーフロー例外、形式例外発生の可能性
Console.Write("x:"); x = int.Parse(Console.ReadLine()); //【追加】
Console.Write("y:"); y = int.Parse(Console.ReadLine()); //【追加】
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構造を置くことができる ・これにより、例外処理内容の重複の整理などが可能になるが、可読性が落ちやすい ・よって、発生する例外の種類によって実行可否の制御をしたい場合などに用いることが多い
p.338 exception08.cs 013行目 正誤
・誤:"{0},{1}"
・正:"{0}"
アレンジ演習:p.338 exception08.cs
・ゼロ除算例外が固定で発生するのではなく、入力値により、ゼロ除算例外、添字範囲外例外、オーバフロー例外等が起こるようにして、 例外ごとの動作を確認しよう ・例外処理範囲の末尾や外に実行文を置いて確認すると良い
作成例
//アレンジ演習:p.338 exception08.cs
using System;
class exception08 {
public static void Main() {
int x = 10, y = 0;
try { //例外処理範囲(外)
try { //例外処理範囲(内)
//下2行でオーバーフロー例外、形式例外発生の可能性
Console.Write("x:"); x = int.Parse(Console.ReadLine()); //【追加】
Console.Write("y:"); y = int.Parse(Console.ReadLine()); //【追加】
Console.WriteLine("{0}", x / y); //ゼロ除算例外発生の可能性
int[] a = new int[2]; //【追加】
a[x] = y; //【追加】添字範囲外例外発生の可能性
Console.WriteLine("a[{0}] = {1}", x, y); //【追加】
}
catch (IndexOutOfRangeException i) { //添字範囲外例外処理
Console.WriteLine("例外処理(内)" + i.Message); //【変更】
}
Console.WriteLine("例外処理範囲(内)終了"); //【追加】tryのネストによりこの記述が可能
}
catch (DivideByZeroException d) { //ゼロ除算例外処理
Console.WriteLine("例外処理(外)" + d.Message); //【変更】
}
}
}
p.338 13.5 独自の例外を作る
・全例外クラスの基本クラスであるExceptionクラスは継承可能なので、プログラマが独自の例外クラスを派生クラスとして定義できる ・また、一部の例外クラスを除き、提供されているExceptionクラスの派生クラスも継承可能 ※ 継承不可なsealedクラスの例: IndexOutOfRangeException ・よって、プログラマが独自の例外クラスを記述する場合、無条件にExceptionクラスを継承するのではなく、 意味合いが近い派生クラスを用いる方が、継承関係を有効に活用できる ・例: ローカルルールで定めた形式に不一致なことを例外で扱うなら、FormatExceptionを継承 ※ チームルールで定めることもある ・独自の例外クラスでは、p.326で示した主なメンバのうち、Message/HelpLinkプロパティ、ToStiringメソッドをオーバライドすることが 推奨される ・なお、exception09.csでは、DivideByZeroExceptionのプロパティやメソッドをオーバライドすることを目的としている ※ チームルールによっては、この行為は推奨されないことがある
アレンジ演習:exception09.cs
・FormatExceptionを継承するMinusValExceptionを自前で定義し、負の数を例外化するために用いよう ・Messageプロパティ、ToStiringメソッドをオーバライドすること ・変数xの値が負の数であれば、MinusValExceptionを投げ、FormatExceptionでcatchしてMessageプロパティ、 ToStiringメソッドを呼び出して確認しよう
作成例
//アレンジ演習: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 MinusValException : FormatException { //【以下追加】形式例外を継承する自前の例外クラス
public new string Message = "負の数にはできません"; //データメンバの上書き
public override string ToString() { //メソッドのオーバライド
return "形式エラー(負の数は不可)";
}
}
class exception09 {
public static void Main() {
int x;
Console.Write("割る数(正の整数)--- "); //【変更】
string strWaru = Console.ReadLine();
try {
x = int.Parse(strWaru);
if (x == 0) { //除数が0であれば
throw new MyEx(); //ゼロ除算例外を継承する自前の例外のオブジェクトを生成して投げる
}
if (x < 0) { //【以下追加】除数が負の数であれば
throw new MinusValException(); //形式例外を継承する自前の例外のオブジェクトを生成して投げる
}
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.ToString()); //【追加】
Console.WriteLine(e.Message);
}
}
}
p.341 checked
・int.Parseメソッド(Int32.Parseメソッド)などでは、変換結果がint.MaxValueを超えたり、
int.MinValueを下回るとオーバーフローとなり、オーバーフロー例外が投げられる
・しかし、計算の途中でオーバーフローが発生した場合、例外を投げない
・例: int a = int.MaxValue, b = a + 1; //bはint.MinValueになる
・このような場合にもオーバーフロー例外が投げられるようにするには、checkedブロックの中に置けば良い
・例: checked { int a = int.MaxValue, b = a + 1; } //オーバーフロー例外が投げられる
※ テキストでは「checked()」も紹介されているが推奨されない場合が多い
アレンジ演習: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;
int w = x + y; //【追加】初期化でもオーバーフロー例外が発生
z = x + y;
Console.WriteLine(z);
}
}
catch (OverflowException o) { //オーバーフロー例外処理
Console.WriteLine(o.Message);
Console.WriteLine(o.StackTrace);
}
}
}
p.341 unchecked
・checkedブロックの中で、その一部において計算時のオーバーフローで例外を投げないようにしたい場合に用いる
アレンジ演習:p.343 checked02.cs・改
・uncheckedを用いて、初期化におけるオーバーフローでは例外を投げないようにできることを確認しよう
作成例
//アレンジ演習:p.343 checked02.cs・改
using System;
class checked02 {
public static void Main() {
int x, y, z;
try {
checked { //このブロックでは計算の途中でオーバーフローが発生した場合、例外を投げる
x = int.MaxValue;
y = 1;
unchecked { //【追加】このブロックでは計算の途中でオーバーフローが発生した場合、例外を投げない
int w = x + y; //初期化でもオーバーフローになるが、例外を投げない
Console.WriteLine("初期化でもオーバーフローになるが、例外を投げないので続行"); //【追加】
}
z = x + y; //オーバーフロー例外が発生し、例外を投げる
Console.WriteLine(z);
}
}
catch (OverflowException o) { //オーバーフロー例外処理
Console.WriteLine(o.Message);
Console.WriteLine(o.StackTrace);
}
}
}
p.344 練習問題 ヒント ex13.cs
・「forループで行い」とあるが、継続/終了条件の記載がないので、これを省いた無限ループにする ・forループ内では動作が分かるように途中の値を表示すると良い ・forループ内にcatchブロックを置くなら、breakも記述することで、例外発生時にforループを抜けるようにしよう ・「byte型変数1 = byte型変数1 * byte型変数2」とすると暗黙的変換ができないというエラーになるので、byte型にキャストするか、 複合代入演算子を用いると良い 例: byte型変数1 = (byte)(byte型変数1 * byte型変数2) 例: byte型変数1 *= byte型変数2
作成例
//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) {
Console.WriteLine("オーバーフロー発生により終了");
break; //ループを抜ける
}
}
}
}