連載

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

Chapter 09 例外処理

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


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

構造化例外処理の確実な終了処理

 これまでに、非構造化例外処理にあって構造化例外処理にはない機能があることは紹介したが、逆の事例もある。つまり、構造化例外処理にあって非構造化例外処理にはない機能も存在する。

 まず、以下のVB 6のソースを見ていただきたい(リスト9-61)。

 1: Private Sub Form_Load()
 2:   Open "c:\testtxt" For Output As #1
 3:   Write #1, 0
 4:   Close #1
 5:
 6:   Open "c:\testtxt" For Input As #1
 7:   Dim i As Integer
 8:   Input #1, i
 9:
10:   On Error GoTo errorHandler
11:   Debug.Print 1 \ i
12:   On Error GoTo 0
13:
14:   Close #1
15:   Debug.Print "正常終了"
16:   Exit Sub
17:
18: errorHandler:
19:   Close #1
20:   Debug.Print Err.Description
21: End Sub
リスト9-61 複数の終了処理が必要となるプログラム

 これを実行するとリスト9-62のようになる。

1: 0 で除算しました。
リスト9-62 リスト9-61の実行結果

 このソースには、2個のOpenステートメントと、3個のCloseステートメントがある。数が一致しないのは、6行目のOpenステートメントに対応するCloseステートメントが2個あるためで、ここではOn Error Gotoステートメントでエラー処理を行った場合と、正常終了を行った場合について、それぞれCloseステートメントが必要とされている。これはResumeステートメントを使って処理を戻す手法を使うことができず、1つのCloseステートメントでは済ませられない事例である。このような状況になると、うっかりCloseステートメントを忘れる場合があるので危険である。特に、処理の流れが複数に分かれたとき、どの流れでも確実にCloseステートメントを配置するように注意を払うのは大変であり、しばしば見落としが発生する。

 このような問題をスマートに解決する方法が、VB.NETの構造化例外処理には用意されている。それを使った例をリスト9-63に示す。

 1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 2:   FileOpen(1, "c:\testtxt", OpenMode.Output)
 3:   Write(1, 0)
 4:   FileClose(1)
 5:
 6:   FileOpen(1, "c:\testtxt", OpenMode.Input)
 7:   Dim i As Integer
 8:   Input(1, i)
 9:
10:   Try
11:     Trace.WriteLine(1 \ i)
12:     Trace.WriteLine("正常終了")
13:   Catch ex As DivideByZeroException
14:     Trace.WriteLine(ex.Message)
15:   Finally
16:     FileClose(1)
17:   End Try
18: End Sub
リスト9-63 Finallyブロックを使用して終了処理を行うプログラム

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

1: 0 で除算しようとしました。
リスト9-64 リスト9-63の実行結果

 このソースを見ると、FileOpen関数が2個で、FileClose関数が2個であることが分かる。つまり、数が一致している。このソースで注目すべき点は、15行目のFinallyブロックである。Finallyブロックは、Tryステートメントの後に、Catchブロックに続けて記述することができる。そして、Finallyキーワード以降に記述されたステートメントは、Tryステートメントから処理が抜け出すときに必ず実行される。つまり、正常終了でも例外発生でも、条件に関係なく、必ず最後に実行されることが保証されている。この場合、11行目の計算が正常に終了しても、DivideByZeroExceptionクラスの例外を発生させても、あるいは、ほかのいかなる例外が発生しても、このTryステートメントのブロックから抜け出すときには必ず実行されることが保証されている。

 これにより、開いたファイルを確実に閉じる、といった処理を容易にわかりやすく記述することができる。これは、非構造化例外処理と比較して、構造化例外処理のほうが強力である理由の1つである。ファイルのクローズ忘れが確実に防止できるとしたら、それは非常にありがたいことではないだろうか?

 なお、Finallyブロックがどのようなケースで実行されるかについては、次節で取り上げている。

Finallyブロックの確実性

 構造化例外処理の確実な終了処理では、Tryステートメントで使用するFinallyブロックについて説明した。では、これはどの程度確実に動作するものなのだろうか。果たして、無条件に頼ってよいものなのだろうか? それを確認するために、さまざまなケースでFinallyブロックを使用するサンプル・プログラムを記述してみた(リスト9-65)。

 1: Private Sub test1()
 2:   Try
 3:     Exit Sub
 4:   Finally
 5:     Trace.WriteLine("Finally called in test1")
 6:   End Try
 7: End Sub
 8:
 9: Private Sub test2()
10:   Do
11:     Try
12:       Exit Do
13:     Finally
14:       Trace.WriteLine("Finally called in test2")
15:     End Try
16:   Loop
17: End Sub
18:
19: Private Sub test3()
20:   Try
21:     Do
22:       Exit Do
23:     Loop
24:   Finally
25:     Trace.WriteLine("Finally called in test3")
26:   End Try
27: End Sub
28:
29: Private Sub test4()
30:   Try
31:     Dim i As Integer
32:     For i = 0 To 9
33:       If i = 5 Then
34:         Try
35:           Exit Sub
36:         Finally
37:           Trace.WriteLine("Finally called in test4A")
38:         End Try
39:       End If
40:     Next
41:   Finally
42:     Trace.WriteLine("Finally called in test4B")
43:   End Try
44: End Sub
45:
46: Private Sub test5()
47:   Try
48:     While True
49:       Do
50:         Do
51:           Exit While
52:         Loop
53:       Loop
54:     End While
55:   Finally
56:     Trace.WriteLine("Finally called in test5")
57:   End Try
58: End Sub
59:
60: Private Sub test6()
61:   Try
62:     Do
63:       Do
64:         Do
65:           Throw New Exception("Sample")
66:         Loop
67:       Loop
68:     Loop
69:   Catch ex As Exception
70:     Trace.WriteLine("Catch called in test6")
71:   Finally
72:     Trace.WriteLine("Finally called in test6")
73:   End Try
74: End Sub
75:
76: Private Sub test7()
77:   Try
78:     '何もない
79:   Finally
80:     Trace.WriteLine("Finally called in test7")
81:   End Try
82: End Sub
83:
84: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
85:   test1()
86:   test2()
87:   test3()
88:   test4()
89:   test5()
90:   test6()
91:   test7()
92: End Sub
リスト9-65 さまざまなケースでのFinallyブロックの動作を検証するプログラム

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

1: Finally called in test1
2: Finally called in test2
3: Finally called in test3
4: Finally called in test4A
5: Finally called in test4B
6: Finally called in test5
7: Catch called in test6
8: Finally called in test6
9: Finally called in test7
リスト9-66 リスト9-65の実行結果

 1〜7行目のコードは、いきなりメソッドから抜け出した場合である。実行結果を見ると、5行目が実行されることが分かる。

 9〜17行目は繰り返しの途中から脱出する場合である。実行結果を見ると、14行目が実行されることが分かる。

 19〜27行目は繰り返しの途中から脱出だが、Tryブロックがループの外側にある点が異なる。実行結果を見ると、25行目が実行されることが分かる。脱出する個所がTryブロックの中でありさえすれば、Tryブロックを記述する場所に関係なく、Finallyブロックが実行される。

 29〜44行目はTryブロックが2重にネストしていて、どちらにもFinallyブロックが存在する場合である。実行結果を見ると、37行目も42行目も、両方とも実行されることが分かる。複数のTryブロックがネストして、複数のFinallyブロックが存在する場合、それらはすべて実行される。そのため、上位あるいは下位のTryブロックにすでにFinallyブロックが存在するかどうかを気にする必要はない。書けば実行される

 46〜58行目は、繰り返しがネストした途中から脱出する場合である。実行結果を見ると、56行目が実行されることが分かる。ネストの深さはFinallyブロックの実行とは関係ない。

 60〜74行目は、繰り返しの途中から例外が発生した場合である。実行結果を見ると、70行目と72行目が実行されることが分かる。深いネスト状態から例外が起きても、Finallyブロックは確実に実行される。

 76〜82行目は、何も問題なくTryブロックの実行を終える場合である。実行結果を見ると、80行目が実行されることが分かる。途中脱出や例外の発生がなくても、FinallyブロックはTryブロックから抜ける際に実行される。

深い階層からの例外とFinallyブロック

 Finallyブロックの確実性で、Finallyブロックがどのようなケースで実行されるかを示したが、ここではもう1つ別のサンプル・プログラムを記述してみた。ここでは、メソッド呼び出しを何重にも行っていて、その内部で例外を発生させている。

 1: Private Sub test1()
 2:   Dim i As Integer, j As Integer
 3:   Trace.WriteLine(i \ j)
 4: End Sub
 5:
 6: Private Sub test2()
 7:   Try
 8:     test1()
 9:   Finally
10:     Trace.WriteLine("Finally called in test2")
11:   End Try
12: End Sub
13:
14: Private Sub test3()
15:   Try
16:     test2()
17:   Finally
18:     Trace.WriteLine("Finally called in test3")
19:   End Try
20: End Sub
21:
22: Private Sub test4()
23:   Try
24:     test3()
25:   Finally
26:     Trace.WriteLine("Finally called in test4")
27:   End Try
28: End Sub
29:
30: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
31:   Try
32:     test4()
33:   Catch ex As DivideByZeroException
34:     Trace.WriteLine("ゼロ除算が発生しました。")
35:   End Try
36: End Sub
リスト9-67 深い階層で発生した例外に対するFinallyブロックの動作を検証するプログラム

 ここではtest1メソッドで例外が発生し、Form1_Loadメソッドでその例外をキャッチしている。そこでポイントになるのは、その中間に位置するtest2〜4メソッドに含まれるFinallyブロックである。例外をキャッチするTryブロックはForm1_Loadメソッド内にあるが、それではこの例外をキャッチしないTryブロックを持つ、test2〜4メソッドに含まれるFinallyブロックはどうなるのだろうか。

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

1: Finally called in test2
2: Finally called in test3
3: Finally called in test4
4: ゼロ除算が発生しました。
リスト9-68 リスト9-67の実行結果

 結果を見て分かるとおり、すべてのFinallyブロックに続くコードが実行されている。Finallyブロックは、そのTryブロックでキャッチするか否かを問わず、すべて有効であるEnd of Article

VB6プログラマーのための入門 Visual Basic .NET 独習講座』

 本記事は、(株)技術評論社が発行する書籍『VB6 プログラマーのための 入門 Visual Basic .NET 独習講座』から
許可を得て転載したものです。

【本連載と書籍の関係について 】
 この書籍は、本フォーラムで連載した「連載 プロフェッショナルVB.NETプログラミング」を大幅に加筆修正し、発行されたものです。技術評論社、および著者である川俣晶氏のご好意により、書籍の内容を本フォーラムの連載記事として掲載させていただけることになりました。

技術評論社の解説ページ

ご注文はこちらから
 

 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