連載

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

第15回 高度な例外処理

(株)ピーデー
川俣 晶
2002/08/31

Page1 Page2 Page3

ネストした例外処理

 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

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

 1: 内部エラー。0除算を確認してください。
サンプル・プログラム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

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

 1: 内部エラー。0除算を確認してください。
サンプル・プログラム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

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

 1: catch by inner block
サンプル・プログラム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

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

 1: catch by inner block
サンプル・プログラム8の実行結果

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

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


 INDEX
  連載 プロフェッショナルVB.NETプログラミング
  第15回 高度な例外処理
    1.例外情報の活用
  2.ネストした例外処理
    3.例外の再発生
 
「プロフェッショナル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メールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間