連載

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

Chapter 09 例外処理

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


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

 VB 6では、エラー発生時のプログラムの挙動を思いどおりにコントロールするために、「On Error Goto」という構文を使用する。しかし、これは非常に古くから使われている構文で、さまざまな点で現在のプログラミングにうまくマッチしない面があった。VB.NETでも、On Error Gotoが使用できるが、VB.NETには新しいエラー処理の機能である「構造化例外処理」と呼ばれるものが新たに加わった。.NET Frameworkでの標準的なエラー処理手段となるため、VB.NETプログラミングでは非常に高い確率で使用することになる。On Error Gotoとどう違うのかも含めてよく確認しておこう。

On Error Gotoを使用した非構造化例外処理

 VB 6では、エラー発生時のプログラムの挙動を思いどおりにコントロールするために、「On Error Goto」という構文を使用する。互換性のために、On Error GotoはVB.NETでもサポートされていて使用できる。VB.NETでは、On Error Gotoは「非構造化例外処理」という名前で呼ばれる

 では、On Error Gotoを使用したVB 6のサンプル・ソースをVB.NET用に書き換えてみよう。まず、VB 6で記述したサンプル・ソースから。

 1: Private Sub Form_Load()
 2:   On Error GoTo errorHandler
 3:   Dim i As Integer
 4:   For i = -2 To 2
 5:     Dim j As Integer
 6:     j = 10 \ i
 7:     Debug.Print j
 8:   Next
 9:   On Error GoTo 0
10:   Exit Sub
11:
12: errorHandler:
13:   If Err = 11 Then
14:     Debug.Print "∞"
15:     Resume Next
16:   End If
17:   Error Err
18: End Sub
リスト9-1 On Error Gotoによりエラー処理をしたプログラム

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

1: -5
2: -10
3:
4: -10
5:  10
6:  5
リスト9-2 リスト9-1の実行結果

 このソース・プログラムは、6行目の割り算で0除算のエラーを発生する可能性がある。その場合、2行目で指定したOn Error Gotoステートメントによって12行目以降が実行される。そして、13行目のIfステートメントで、発生したのが0除算かどうかを確認し、そうであれば∞記号を表示して、エラーが発生した次のステートメントから実行を再開させている。再開は15行目のResumeステートメントで行っている。

 では、なるべくこれに近い形で、実行可能なVB.NETのソースを記述するとどうなるだろうか。実際に記述してみたのがリスト9-3である。

 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 i As Integer
 4:   For i = -2 To 2
 5:     Dim j As Integer
 6:     j = 10 \ i
 7:     Trace.WriteLine(j)
 8:   Next
 9:   On Error GoTo 0
10:   Exit Sub
11:
12: errorHandler:
13:   If Err.Number = 11 Then
14:     Trace.WriteLine("∞")
15:     Resume Next
16:   End If
17:   Error Err.Number
18: End Sub
リスト9-3 リスト9-1に近い形で記述したVB.NETのプログラム

 これを実行すると以下のようになる。ただし、システムからのメッセージは除いてある。

1: -5
2: -10
3:
4: -10
5: 10
6: 5
リスト9-4 リスト9-3の実行結果

 見てのとおり、ほとんど同じとなっている。特に目立つのは13行目で、デフォルト・プロパティがないため、ErrオブジェクトのNumberプロパティを明示的に記述する必要が生じている。遠いむかしのBASIC言語の知識で、Errは数値を持つシステム変数だと思っていた方は、知識をアップデートする必要があるだろう。

 ただし、1つだけ注意すべきことがある。実は、このコードは、6行目の“\”を“/”に変更すると、VB 6とVB.NETで挙動が変わってしまう。VB 6で“\”を“/”に変更した場合、発生するエラーは11番(0除算)だが、VB.NETでは6番(オーバーフロー)が発生する。表面的な構文は同じだが、必ず同じ番号のエラーをつかまえられるわけではないことに注意が必要だろう。

Try〜Catchを使用した構造化例外処理

 「On Error Goto」という構文は、筆者が使っていた昔懐かしい1979年発売のNEC PC-8001のN-BASICにも搭載されていたほど歴史の古い構文である。それゆえに、実際VB 6でプログラミングしていると、ほかの機能が進歩しているのに対して、エラー処理だけはOn Error Gotoという古い構造がそのまま使われている点が非常にアンバランスであった。例えば、On Error Gotoは行番号やラベルを必須とするため、構造化プログラミングと相性が悪いし、エラーの種類を調べるために主に番号を用いるのもソースのわかりやすさを損なう。前節で述べたように、VB.NETでも、On Error Goto(非構造化例外処理)が使用できるが、これは互換のための機能といえる。それに代わって、VB.NETには「構造化例外処理」と呼ばれる新しいエラー処理機能が用意されている

 VB 6でOn Error Gotoを用いて作成されたソース・コードについては、VB.NETではOn Error Gotoではなく、構造化例外処理を使用するように書き直すことができる。リスト9-5は実際にリスト9-3を構造化例外処理を使用して書き直した例である。

 1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 2:   Dim i As Integer
 3:   For i = -2 To 2
 4:     Dim j As Integer
 5:     Try
 6:       j = 10 \ i
 7:     Catch ex As DivideByZeroException
 8:       Trace.WriteLine("∞")
 9:     End Try
10:     Trace.WriteLine(j)
11:   Next
12: End Sub
リスト9-5 リスト9-3を構造化例外処理を使用して記述したプログラム

 これを実行すると以下のようになる。システムからのメッセージは除いてある。

1: -5
2: -10
3:
4: -10
5: 10
6: 5
リスト9-6 リスト9-5の実行結果

 何もかも変わってしまったように見えるが、機能的にはまったく等価である。

 On Error Gotoは、有効なラベルを指定したOn Error Gotoから、On Error Goto 0までの範囲でエラーの発生を検出するが、構造化例外処理では、Tryキーワードから、Catchキーワードの範囲でエラーの発生を検出する。エラーが発生すると、On Error Gotoの場合は指定ラベルに処理がジャンプするが、構造化例外処理では、CatchキーワードからEnd Tryキーワードまでの範囲が実行される。こう書くと、あまり変わらないように思うかもしれないが、ソース中でOn Error Goto〜On Error Goto 0ではさんだ範囲と、Try〜Catchではさんだ範囲がまるで違っていることが分かるだろう。前者はかなり広い範囲をはさんでいるが、後者は1行しかはさんでいない。この相違の理由は何だろうか?

 それは、例外処理終了時に処理を元の流れに戻すことができないからだ。On Error Gotoを使用しているときは、Resumeステートメントで簡単に元の処理の流れに戻ることができる。しかし、構造化例外処理にはそのような機能が存在しない。元の処理の流れに戻るためには、End Tryの次に、戻るべき処理の流れが存在していなければならない。そこで、必然的にエラーを検出する範囲が変わってしまうのである。

 次に注目すべき点は、発生したエラーの種類を判定するIfステートメントがどこにもないことである。エラーの種類は、Catchキーワードの後に書かれた引数によって指定されている。ここでは、DivideByZeroExceptionという名前がエラーの種類を示している。これは、0除算を意味する例外クラスの名前だが、Catchブロックに記述されることにより、0除算だけを受け止めるという効能を発揮する。つまり、

Catch 〜 As DivideByZeroException

と記述していれば、常に0除算の場合のみCatchブロック以降を実行し、それ以外のエラーは何も指定されなかったのと同じ結果になるのである。そのため、構造化例外処理を使用する場合には、エラーの種類を判定するIfステートメントは不要となり、また、エラーの種類は番号ではなくクラス名で判別される。

 最後に、7行目の引数として指定されたexという名前にはどのような意味があるのだろうか。これはVB 6のErrオブジェクトに似た役割を持ったオブジェクトが格納される引数である。このソースでは参照していないが、少し込み入った処理では参照が必要になる(次のリスト9-7で解説)。

 さて、構造化例外処理では特定の種類のエラーだけを処理できるが、あえてVB 6の場合のように、Catchブロックの中でエラーの種類を判定するように記述することはできるだろうか? リスト9-7はそれを記述してみた例である。

 1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 2:   Dim i As Integer
 3:   For i = -2 To 2
 4:     Dim j As Integer
 5:     Try
 6:       j = 10 \ i
 7:     Catch ex As Exception
 8:       If TypeOf ex Is DivideByZeroException Then
 9:         Trace.WriteLine("∞")
10:       Else
11:         Throw
12:       End If
13:     End Try
14:     Trace.WriteLine(j)
15:   Next
16: End Sub
リスト9-7 Catchブロックの中でエラーの種類を判定するようにしたプログラム

 これを実行すると以下のようになる。ただし、システムからのメッセージは除いてある。

1: -5
2: -10
3:
4: -10
5: 10
6: 5
リスト9-8 リスト9-7の実行結果

 このソース・コードで注目すべき点は、7行目のCatchブロックで指定されたデータ型がDivideByZeroExceptionではなく、Exceptionになっている点である。エラーを表す例外クラスにはそれぞれ継承関係があり、スーパー・クラスの名前を指定しておけば、サブクラスの例外もキャッチすることができる(継承については、継承とポリモーフィズムを参照)。すべての例外クラスは、System.Exceptionクラスを継承して作られているので、このクラスを指定しておけば、すべての例外をキャッチすることができる。なお、ここでは“System.”を省略して“Exception”とだけ書いている。

 次に注目してほしいのは、引数exが活用されている点である。8行目のIfステートメントで、引数exの型がDivideByZeroExceptionかどうかを、TypeOfキーワードを用いて調べている。Catchブロックの引数がException型だからといって、exに含まれている内容が本当にException型とは限らない。もしかしたら、Exceptionクラスを継承した別のクラスのインスタンスかもしれないのである。それをTypeOfキーワードを用いてチェックすることができる(引数のさらなる活用については、例外情報の活用を参照)。

 もう1点見ておきたいのが、11行目のThrowステートメントである。Throwステートメントは、例外を明示的に発生させる機能を持ったもので、非構造化例外処理のErrorステートメントあるいはErrオブジェクトのRaiseメソッドに相当する。Catchブロック内で引数なしで使用すると、Catchされた例外を再発生する効能がある。これについては、例外の再発生で詳しく説明を行っている。

 なお、このようなすべての例外をまとめてキャッチする方法は、実際にはあまり使われない。なぜなら、1つのTryステートメントに対応するCatchブロックはいくつでも書けるので、Ifステートメントで種類を判定しなくても、Catchブロックを増やすだけで済むからである。そのようなサンプル・ソースは、複数のエラーを扱うで示す。


 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