連載

改訂版
プロフェッショナルVB.NETプログラミング

Chapter 09 例外処理

株式会社ピーデー 川俣 晶
2004/06/10


 本記事は、(株)技術評論社が発行する書籍『VB6プログラマーのための入門 Visual Basic .NET 独習講座』の一部分を許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。

例外情報の活用

 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ブロックを入れ替えたプログラム

 これを実行すると以下のようになる。

1: 内部エラー。0除算を確認してください。
リスト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つのメソッドに分割したプログラム

 これを実行すると以下のようになる。

1: 内部エラー。0除算を確認してください。
リスト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ブロックを確認するためのプログラム

 これを実行すると以下のようになる。

1: catch by inner block
リスト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クラスを指定したプログラム

 これを実行すると以下のようになる。

1: catch by inner block
リスト9-34 リスト9-33の実行結果

 0除算の例外は、ソース9行目のCatch ex As DivideByZeroExceptionではなく、Catch ex As Exceptionでキャッチされてしまっている。これは、Exceptionクラスを継承して、DivideByZeroExceptionクラスが作られているためである。DivideByZeroExceptionクラスはExceptionクラスとして扱うこともできるので、6行目でDivideByZeroExceptionクラスの例外をキャッチすることができる。

 このように、例外には継承関係があることに注意が必要である。特に、例外がネストするような状況では、.NET Frameworkのクラス・ライブラリ・リファレンスを見て、継承関係を確認しておくとよいだろう。


 INDEX
  [連載] 改訂版 プロフェッショナルVB.NETプログラミング
  Chapter 09 例外処理
    1.On Error Gotoを使用した非構造化例外処理/Try〜Catchを使用した構造化例外処理
    2.構造化例外処理と非構造化例外処理の混用/On Error Resume Next/複数のエラーを扱う
  3.例外情報の活用/ネストした例外処理
    4.例外の再発生/プログラムにより明示的に例外を発生させる
    5.例外の自作/InnerExceptionについて
    6.構造化例外処理の確実な終了処理/Finallyブロックの確実性/深い階層からの例外とFinallyブロック
 
「改訂版 プロフェッショナルVB.NETプログラミング 」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH