連載
プロフェッショナルVB.NETプログラミング
第15回 高度な例外処理
(株)ピーデー
川俣 晶
2002/08/31
|
|
ネストした例外処理
On Error Goto文はネストできないが、例外ブロックはネストできる。つまり、Try文とEnd Try文の間に、ほかのTry文とEnd Try文を入れることができるという意味である。実際に、それを記述した例を以下に示す。
1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
2: Dim a(9) As Integer
3: Try
4: Dim i As Integer
5: For i = 0 To 10
6: Try
7: a(i) = 10 \ i
8: Catch ex As DivideByZeroException
9: a(i) = 0
10: End Try
11: Trace.WriteLine(a(i))
12: Next
13: Catch ex As IndexOutOfRangeException
14: Trace.WriteLine("内部エラー。配列のサイズを確認してください。")
15: End Try
16: End Sub
|
|
例外ブロックをネストさせたVB.NETのサンプル・プログラム4 |
これを実行すると以下のようになる。
1: 0
2: 10
3: 5
4: 3
5: 2
6: 2
7: 1
8: 1
9: 1
10: 1
11: 内部エラー。配列のサイズを確認してください。
|
|
サンプル・プログラム4の実行結果 |
これを見て分かるとおり、7行目で発生した0除算のエラーは、8行目のcatch文によってキャッチされるが、7行目で発生した配列の範囲を超えた添え字の使用は、13行目のcatch文でキャッチされる。これらは、別々のTry文に対応するものであるが、どちらも有効に機能している。
さて、catch文は、ネストした例外ブロックのどの階層にあっても機能する。そのことを確認するために、上記サンプル・プログラムの2つのcatch文の位置を入れ替えてみよう。
1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
2: Dim a(9) As Integer
3: Try
4: Dim i As Integer
5: For i = 0 To 10
6: Try
7: a(i) = 10 \ i
8: Catch ex As IndexOutOfRangeException
9: Trace.WriteLine("内部エラー。配列のサイズを確認してください。")
10: End Try
11: Trace.WriteLine(a(i))
12: Next
13: Catch ex As DivideByZeroException
14: Trace.WriteLine("内部エラー。0除算を確認してください。")
15: End Try
16: End Sub
|
|
サンプル・プログラム4の2つのcatch文を入れ替えたサンプル・プログラム5 |
これを実行すると以下のようになる。
見てのとおり、結果はだいぶ違っている。catch文がどの階層に書かれていても有効であるのは変わりないが、例外処理終了後に実行を継続する位置が変わってくる。具体的には、End Try文の次から処理を継続するのが標準的な状況だが、違う階層の例外ブロックであれば、当然End Try文の位置が違うので、処理を継続する位置が違ってくる。
ネストは、別メソッドに分かれても有効である。呼び出し元のメソッドと、呼び出し先のメソッドの両方に例外ブロックがあってもよい。上記のサンプル・ソースを、2つのメソッドに分割してみた例を以下に示す。
1: Private Sub Calc()
2: Dim a(9) As Integer
3: Dim i As Integer
4: For i = 0 To 10
5: Try
6: a(i) = 10 \ i
7: Catch ex As IndexOutOfRangeException
8: Trace.WriteLine("内部エラー。配列のサイズを確認してください。")
9: End Try
10: Trace.WriteLine(a(i))
11: Next
12: End Sub
13:
14: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
15: Try
16: Calc()
17: Catch ex As DivideByZeroException
18: Trace.WriteLine("内部エラー。0除算を確認してください。")
19: End Try
20: End Sub
|
|
サンプル・プログラム5を2つのメソッドに分割したサンプル・プログラム6 |
これを実行すると以下のようになる。
見てのとおり、例外ブロックの効力は、メソッドが分かれても同様に機能する。
では、同じ例外に対するcatch文が異なる階層に存在した場合、どれが有効になるのだろうか? 実際に確認するサンプル・プログラムを記述してみた。
1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
2: Try
3: Try
4: Dim a As Integer = 1, b As Integer = 0
5: Trace.WriteLine(a \ b)
6: Catch ex As DivideByZeroException
7: Trace.WriteLine("catch by inner block")
8: End Try
9: Catch ex As DivideByZeroException
10: Trace.WriteLine("catch by outer block")
11: End Try
12: End Sub
|
|
有効となるcatch文を確認するためのVB.NETのサンプル・プログラム7 |
これを実行すると以下のようになる。
見てのとおり、より内側のcatch文にキャッチされている。つまり、より外側の例外処理を使いたくない場合は、より内側にcatch文を用意して、それでキャッチすればよいわけである。逆に、より外側のcatch文にキャッチさせたければ、同じ例外に対するcatch文をより内側に書いてはならないことになる。
この条件は、例外がクラスであり、継承関係を持つことから、慎重に確認する必要がある。一見、違う例外に見えても、継承関係によりキャッチされてしまう場合があるからだ。以下は、内側のcatch文で、Exceptionクラス(System.Exceptionクラス)を指定するようにサンプル・プログラムを書き換えた例である。
1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
2: Try
3: Try
4: Dim a As Integer = 1, b As Integer = 0
5: Trace.WriteLine(a \ b)
6: Catch ex As Exception
7: Trace.WriteLine("catch by inner block")
8: End Try
9: Catch ex As DivideByZeroException
10: Trace.WriteLine("catch by outer block")
11: End Try
12: End Sub
|
|
内側のcatch文でExceptionクラスを指定したVB.NETのサンプル・プログラム8 |
これを実行すると以下のようになる。
見てのとおり、0除算の例外は、9行目のCatch ex As DivideByZeroExceptionではなく、Catch ex As Exceptionでキャッチされてしまっている。これは、Exceptionクラスを継承して、DivideByZeroExceptionクラスが作られているためである。DivideByZeroExceptionクラスはExceptionクラスとして扱うこともできるので、6行目でDivideByZeroExceptionクラスの例外をキャッチすることができる。
このように、例外には継承関係があることに注意が必要である。特に例外がネストするような状況では、APIリファレンスを見て、継承関係を確認しておくとよいだろう。
Insider.NET 記事ランキング
本日
月間