例外処理
例外
プログラム実行時に発生する異常を,例外 (exception) と呼びます。
例外処理機構を利用することで,問題の検出と解決を分離して記述できるようになります。
try-catch-finally 文
次のように try-catch-finally 文を使うと,プログラムの実行時エラー (例外) を処理できます。
try
{
// 例外が発生する可能性のある処理
}
catch (例外型1 変数)
{
// 例外をキャッチした場合に行う処理
}
...
finally
{
// 最後に必ず行うべき処理
}
catch 節は catch (例外型) { ... } または catch { ... } と書くこともできます。
catch 節,finally 節はどちらか一方のみ省略できます。
次のプログラムは,try-catch 文を用いたプログラムの例です。
using System;
class Program
{
static void Main()
{
try
{
int a = 1, b = 0;
int c = a / b;
}
catch (OverflowException e)
{
Console.WriteLine("OverflowException をキャッチ");
Console.WriteLine(e.Message);
}
catch (DivideByZeroException e)
{
Console.WriteLine("DivideByZeroException をキャッチ");
Console.WriteLine(e.Message);
}
catch (Exception e)
{
Console.WriteLine("Exception をキャッチ");
Console.WriteLine(e.Message);
}
}
}
DivideByZeroException をキャッチ
0 で除算しようとしました。
DivideByZeroException は,DivideByZeroException と Exception の両方にマッチします。
ただし,DivideByZeroException の catch 節が先に書かれているので,この節でキャッチされます。
次のプログラムは,try-finally 文を使ったプログラムの例です。
finally 節で Dispose メソッドを実行することにより,例外が発生したときでも確実にリソースが解放されるようにしています。
StreamReader reader = new StreamReader("hoge.txt");
try
{
text = reader.ReadToEnd();
}
catch (IOException e)
{
Console.WriteLine("IOException をキャッチ");
console.WriteLine(e.Message);
}
finally
{
if (reader != null)
{
reader.Dispose();
}
}
例外クラス
組込みの代表的な例外クラスには,次のようなものがあります。
例外クラス | 説明 |
---|---|
Exception | すべての例外クラスの基底 |
DivideByZeroException | ゼロ除算を実行した |
IndexOutOfRangeException | 範囲外の添字を指定した |
OverflowException | 桁あふれが発生した |
NullReferenceException | 空のオブジェクトにアクセスした |
InvalidCastException | 無効なキャストを実行した |
InvalidOperationException | 無効な操作を実行した |
Exception クラス (System 名前空間) は,すべての例外クラスの基底クラスです。
例外のスロー
次のプログラムは,例外オブジェクトを生成してスローする例です。
using System;
class Program
{
// x の y 乗を返す
static int Power(int x, int y)
{
if (y < 0)
{
throw new ArgumentOutOfRangeException(
"y", "0 以上の整数を指定する必要があります。"); // 例外をスロー
}
int ret = 1;
for (int i = 0; i < y; i++)
{
ret *= x;
}
return ret;
}
static void Main()
{
try
{
int n = Power(2, -1);
Console.WriteLine(n);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine("キャッチした例外: " + e.Message);
}
}
}
キャッチした例外: 0 以上の整数を指定する必要があります。
パラメーター名: y
例外オブジェクトを生成するには,普通のクラスからオブジェクトを生成するのと同様に new 演算子を使用します。
ここでは,ArgumentOutOfRangeException 例外にメッセージ "引数 y は 0 以上である必要があります" を設定して,スローしています。
例外の再スロー
catch 節でキャッチした例外は,その catch 節で再びスローすることもできます。
こうすることで,例外の情報をメソッドの呼び出し元に伝播させることができます。
using System;
class Program
{
static void Method2()
{
throw new Exception(); // 例外をスロー
}
static void Method1()
{
try
{
Method2();
}
catch (Exception e)
{
throw; // キャッチした例外を再スロー
}
}
static void Main()
{
Method1();
}
}
ハンドルされていない例外: System.Exception: 種類 'System.Exception' の例外がスローされました。
場所 Program.Method2() 場所 C:\projects\Test1\Test1\Program.cs:行 11
場所 Program.Method1() 場所 C:\projects\Test1\Test1\Program.cs:行 22
場所 Program.Main() 場所 C:\projects\Test1\Test1\Program.cs:行 28
Method2 では自分で例外オブジェクトを作成してスローしています。
Method1 では Method2 から来た例外をキャッチし,再スローしています。
例外を再スローするとき,スタックトレースを保持するためには,throw e; ではなく throw; と書く必要があります。
もし throw e; と書いてしまうと,スタックトレースがリセットされ,例外の発生原因の特定が難しくなってしまう場合があるため,注意が必要です。
実際,Method1 を次のように変えて throw e; で例外を再スローすると,スタックトレースがリセットされてしまうことがわかります。
static void Method1()
{
try
{
Method2();
}
catch (Exception e)
{
throw e; // キャッチした例外を再スロー
}
}
ハンドルされていない例外: System.Exception: 種類 'System.Exception' の例外がスローされました。
場所 Program.Method1() 場所 C:\projects\Test1\Test1\Program.cs:行 22
場所 Program.Main() 場所 C:\projects\Test1\Test1\Program.cs:行 28