|
|
連載
改訂版
プロフェッショナルVB.NETプログラミング
Chapter 09 例外処理
株式会社ピーデー
川俣 晶
2004/06/10 |
|
|
errオブジェクトや例外オブジェクトには、さまざまな有益な情報が含まれている。
VB 6でエラーが発生した場合、エラーに関する情報がerrオブジェクトに格納される。その情報をいくつか表示させてみた例をリスト9-19に示す。
1: Private Sub Form_Load()
2: On Error GoTo errorHandler
3: Dim a As Integer, b As Integer
4: a = 1
5: b = 0
6: Debug.Print a \ b
7: Exit Sub
8:
9: errorHandler:
10: Debug.Print "Description:"
11: Debug.Print Err.Description
12: Debug.Print "HelpContext:"
13: Debug.Print Err.HelpContext
14: Debug.Print "HelpFile:"
15: Debug.Print Err.HelpFile
16: Debug.Print "LastDllError:"
17: Debug.Print Err.LastDllError
18: Debug.Print "Number:"
19: Debug.Print Err.Number
20: Debug.Print "Source:"
21: Debug.Print Err.Source
22: End Sub
|
|
リスト9-19 errオブジェクトに格納されている情報を表示するプログラム
|
これを実行すると以下のような結果になる。
1: Description:
2: 0 で除算しました。
3: HelpContext:
4: 1000011
5: HelpFile:
6: D:\WINNT\Help\VBENLR98.CHM
7: LastDllError:
8: 0
9: Number:
10: 11
11: Source:
12: Project1
|
|
リスト9-20 リスト9-19の実行結果
|
これに相当するソース・コードをVB.NETで記述することは容易である。リスト9-21のように記述すれば、ある程度近い結果を得ることができる。
1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
2: On Error GoTo errorHandler
3: Dim a As Integer, b As Integer
4: a = 1
5: b = 0
6: Trace.WriteLine(a \ b)
7: Exit Sub
8:
9: errorHandler:
10: Trace.WriteLine("Description:")
11: Trace.WriteLine(Err.Description)
12: Trace.WriteLine("HelpContext:")
13: Trace.WriteLine(Err.HelpContext)
14: Trace.WriteLine("HelpFile:")
15: Trace.WriteLine(Err.HelpFile)
16: Trace.WriteLine("LastDllError:")
17: Trace.WriteLine(Err.LastDllError)
18: Trace.WriteLine("Number:")
19: Trace.WriteLine(Err.Number)
20: Trace.WriteLine("Source:")
21: Trace.WriteLine(Err.Source)
22: End Sub
|
|
リスト9-21 リスト9-19と同等なVB.NETのプログラム
|
これを実行すると以下のようになる。
1: Description:
2: 0 で除算しようとしました。
3: HelpContext:
4: 0
5: HelpFile:
6:
7: LastDllError:
8: 0
9: Number:
10: 11
11: Source:
12: Sample001n2
|
|
リスト9-22 リスト9-21の実行結果
|
しかし、例外を使用した場合の例外オブジェクトは、errオブジェクトとは必ずしも互換性がない。リスト9-23は、例外オブジェクトのいくつかのプロパティを表示させてみるサンプル・プログラムである。
1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
2: Try
3: Dim a As Integer = 1, b As Integer = 0
4: Trace.WriteLine(a \ b)
5: Exit Sub
6: Catch ex As DivideByZeroException
7: Trace.WriteLine("HelpLink:")
8: Trace.WriteLine(ex.HelpLink)
9: Trace.WriteLine("Message:")
10: Trace.WriteLine(ex.Message)
11: Trace.WriteLine("Source:")
12: Trace.WriteLine(ex.Source)
13: Trace.WriteLine("StackTrace:")
14: Trace.WriteLine(ex.StackTrace)
15: Trace.WriteLine("TargetSite:")
16: Trace.WriteLine(ex.TargetSite)
17: End Try
18: End Sub
|
|
リスト9-23 例外オブジェクトのプロパティを表示するプログラム
|
これを実行すると以下のようになる。
1: HelpLink:
2:
3: Message:
4: 0 で除算しようとしました。
5: Source:
6: Sample001n
7: StackTrace:
8: at Sample001n.Form1.Form1_Load(Object sender, EventArgs e) in Q:\aWrite\@it\vbn\013\smpl\Sample001n\Form1.vb:line 48
9: TargetSite:
10: Void Form1_Load(System.Object, System.EventArgs)
|
|
リスト9-24 リスト9-23の実行結果
|
見てのとおり、それぞれが扱う情報は同じではない。特に、エラー番号情報の有無や、ヘルプ指定方法の違いなど、コードを機械的に書き換えるだけでは対応できない要素も多い。これらの情報に強く依存することは少ないと思うが、On Error Gotoステートメントから例外に変更する場合は、これらの情報に依存していないか事前に確認しておくとよいだろう。
On Error Gotoステートメントはネストできないが、例外ブロックはネストできる。つまり、TryステートメントとEnd Tryステートメントの間に、ほかのTryステートメントとEnd Tryステートメントを入れることができるという意味である。実際に、それを記述した例をリスト9-25に示す。
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
|
|
リスト9-25 例外ブロックをネストさせたプログラム
|
これを実行すると以下のようになる。
1: 0
2: 10
3: 5
4: 3
5: 2
6: 2
7: 1
8: 1
9: 1
10: 1
11: 内部エラー。配列のサイズを確認してください。
|
|
リスト9-26 リスト9-25の実行結果
|
ソースの7行目で発生した0除算のエラーは、8行目のCatchブロックによってキャッチされるが、7行目で発生した配列の範囲を超えた添え字の使用は、13行目のCatchブロックでキャッチされる。これらは、別々のTryステートメントに対応するものであるが、どちらも有効に機能している。
さて、Catchブロックはネストした例外ブロックのどの階層にあっても機能する。そのことを確認するために、リスト9-25の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
|
|
リスト9-27 リスト9-25の2つのCatchブロックを入れ替えたプログラム
|
これを実行すると以下のようになる。
|
リスト9-28 リスト9-27の実行結果
|
見てのとおり、結果はだいぶ違っている。Catchブロックがどの階層に書かれていても有効であるのは変わりないが、例外処理終了後に実行を継続する位置が変わってくる。具体的には、End Tryステートメントの次から処理を継続するのが標準的な状況だが、違う階層の例外ブロックであれば、当然End Tryステートメントの位置が違うので、処理を継続する位置が違ってくる。
ネストは別メソッドに分かれても有効である。呼び出し元のメソッドと、呼び出し先のメソッドの両方に例外ブロックがあってもよい。上記のサンプル・ソースを、2つのメソッドに分割してみた例をリスト9-29に示す。
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
|
|
リスト9-29 リスト9-27を2つのメソッドに分割したプログラム
|
これを実行すると以下のようになる。
|
リスト9-30 リスト9-27の実行結果
|
このように、例外ブロックの効力は、メソッドが分かれても同様に機能する。
では、同じ例外に対するCatchブロックが異なる階層に存在した場合、どれが有効になるのだろうか? 実際に確認するサンプル・プログラムを記述してみた(リスト9-31)。
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
|
|
リスト9-31 有効となるCatchブロックを確認するためのプログラム
|
これを実行すると以下のようになる。
|
リスト9-32 リスト9-31の実行結果
|
見てのとおり、より内側のCatchブロックにキャッチされている。つまり、より外側の例外処理を使いたくない場合は、より内側にCatchブロックを用意して、それでキャッチすればよいわけである。逆に、より外側のCatchブロックにキャッチさせたければ、同じ例外に対するCatchブロックをより内側に書いてはならないことになる。
この条件は、例外がクラスであり、継承関係を持つことから、慎重に確認する必要がある(継承については継承とポリモーフィズムを参照)。一見、違う例外に見えても、継承関係によりキャッチされてしまう場合があるからだ。リスト9-33は、内側のCatchブロックで、Exceptionクラス(System.Exceptionクラス)を指定するようにリスト9-31を書き換えた例である。
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
|
|
リスト9-33 内側のCatchブロックでExceptionクラスを指定したプログラム
|
これを実行すると以下のようになる。
|
リスト9-34 リスト9-33の実行結果
|
0除算の例外は、ソース9行目のCatch ex As DivideByZeroExceptionではなく、Catch ex As Exceptionでキャッチされてしまっている。これは、Exceptionクラスを継承して、DivideByZeroExceptionクラスが作られているためである。DivideByZeroExceptionクラスはExceptionクラスとして扱うこともできるので、6行目でDivideByZeroExceptionクラスの例外をキャッチすることができる。
このように、例外には継承関係があることに注意が必要である。特に、例外がネストするような状況では、.NET Frameworkのクラス・ライブラリ・リファレンスを見て、継承関係を確認しておくとよいだろう。