連載

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

Chapter 09 例外処理

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


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

例外の自作

 VB 6では、ErrorステートメントやErr.Raiseメソッドを用いてエラーを意図的に発生させることができることは、前節で述べた。このとき、あえてエラーが定義されていない番号を指定することもできる。これは自作プログラム内で発生する各種独自エラーを、VB 6のエラー処理メカニズムを利用して処理するために使用される。これをうまく使えば、プログラムをきれいにまとめられる可能性がある。リスト9-49に、独自エラーを、VB 6のエラー処理機能を用いて処理した例を示す。

 1: Private Sub checkLicense()
 2:   If Dir("c:\user\license.txt") = "" Then
 3:     Err.Raise 1234
 4:   End If
 5: End Sub
 6:
 7: Private Sub Form_Load()
 8:   On Error GoTo licenseErrorHandler
 9:   checkLicense
10:   Exit Sub
11:
12: licenseErrorHandler:
13:   If Err.Number <> 1234 Then
14:     Err.Raise Err.Number
15:   Else
16:     MsgBox "ライセンスファイルがありません。使用するためにはライセンス認証が必要です。"
17:     End
18:   End If
19: End Sub
リスト9-49 VB 6のエラー処理機能を用いて独自のエラーを処理しているプログラム

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

●図9-50 リスト9-49の実行結果

 このソース・コードは、3行目のErr.Raiseメソッドによって独自エラーを発生させている。このソースでは、適当に与えた1234という番号によって、独自エラーを識別するようにしている。このエラーは、13行目のIfステートメントによる判断され、1234番以外は通常のエラー処理に移行し、1234番ならメッセージを出力して終了する。

 では、これと同等のソース・コードがVB.NETでも記述できるだろうか? 以下は記述してみた例である(リスト9-51)。

 1: Private Sub checkLicense()
 2:   If Dir("c:\user\license.txt") = "" Then
 3:     Err.Raise(1234)
 4:   End If
 5: End Sub
 6:
 7: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 8:   On Error GoTo licenseErrorHandler
 9:   checkLicense()
10:   Exit Sub
11:
12: licenseErrorHandler:
13:   If Err.Number <> 1234 Then
14:     On Error GoTo 0
15:   Else
16:     MsgBox("ライセンスファイルがありません。使用するためにはライセンス認証が必要です。")
17:     End
18:   End If
19: End Sub
リスト9-51 リスト9-49と同等な処理を記述したVB.NETのプログラム

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

●図9-52 リスト9-51の実行結果

 見てのとおり、ほとんど変わりないものが記述できる。しかし、VB.NETの場合、構造化例外処理を用いて処理するという選択もある。もし、構造化例外処理を使うとすれば、どのように記述できるだろうか? 実際に記述してみたのがリスト9-53の例である。

 1: Class LicenseErrorException
 2:   Inherits Exception
 3:   Public Sub New(ByVal msg As String)
 4:     MyBase.New(msg)
 5:   End Sub
 6: End Class
 7:
 8: Private Sub checkLicense()
 9:   If Dir("c:\user\license.txt") = "" Then
10:     Throw New LicenseErrorException("ライセンスファイルがありません。")
11:   End If
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:     checkLicense()
17:   Catch ex As LicenseErrorException
18:     MsgBox(ex.Message & "使用するためにはライセンス認証が必要です。")
19:     End
20:   End Try
21: End Sub
リスト9-53 リスト9-51を、構造化例外処理を用いて記述したプログラム

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

●図9-54 リスト9-53の実行結果

 このソースのポイントは、1〜6行目で定義されたクラスである。このクラスは、Exception(System.Exception)クラスを継承した例外クラスである(継承については、継承とポリモーフィズムを参照)。これらのクラスは、システムが提供するDivideByZeroExceptionなどと同じように例外機能で使用することができる。実際に、10行目のThrowステートメントで例外を発生させ、17行目のCatchブロックでそれをキャッチしている。この例外クラスでは、例外を投げる際に引数に理由を示す文字列を記述することができる。このサンプル・ソースには「ライセンスファイルがありません」という状況しか存在しないが、ライセンスファイルが正しくなかったり、期限が切れているような場合にも、それなりのメッセージを表示させたい場合は、このような引数が有効である。もちろん、それぞれの理由ごとに例外クラスを作成して区別するという方法もある。

 さて、念のため、細かい部分を説明しておこう。2行目のInheritsステートメントは、Exceptionクラスを継承したクラスであることを示している。3〜5行目はコンストラクタである。4行目のMyBaseは、スーパークラスのコンストラクタを呼び出すために使用される。つまり、4行目では、引数を親クラスのコンストラクタに引き渡しているわけである。Exceptionクラスにはメッセージを受け取って保持する機能があるので、メッセージの保存はそれに任せている。

InnerExceptionについて

 あるエラーが発生した場合、それを別のエラーとして扱いたい場合がある。例えば、ファイルが見つからないときに、それを「ファイルが見つからない」というエラーとして処理するのではなく、何らかのパラメータエラーとして扱いたい場合があるだろう。つまり、プロシージャに渡すパラメータの値の問題であると捉えたい、というわけだ。そういう場合、1回エラーをトラップしてから、もう一度エラーを発生させるという方法が可能である。実際にVB 6でそれを記述した例をリスト9-55に示す。

 1: Private Function GetData(ByVal filename As String)
 2:   On Error GoTo errorHandlerInCalc
 3:   Open "c:\" & filename & ".txt" For Input As #1
 4:   Close 1
 5:   Exit Function
 6:
 7: errorHandlerInCalc:
 8:   If Err.Number <> 53 Then
 9:     Err.Raise Err.Number
10:   Else
11:     Err.Raise 1234
12:   End If
13: End Function
14:
15: Private Sub Form_Load()
16:   On Error GoTo errorHandlerInMain
17:   Debug.Print GetData("app_define_data")
18:   Exit Sub
19:
20: errorHandlerInMain:
21:   If Err.Number <> 1234 Then
22:     Err.Raise Err.Number
23:   Else
24:     MsgBox "内部パラメータエラーです。"
25:   End If
26: End Sub
リスト9-55 エラー処理の中で、さらにエラーを発生させているプログラム

 これを実行すると図9-56のようになる。

●図9-56 リスト9-55の実行結果

 このソースは、ほとんどそのままVB.NETでも動作する(リスト9-57)。

 1: Private Function GetData(ByVal filename As String)
 2:   On Error GoTo errorHandlerInCalc
 3:   FileOpen(1, "c:\" & filename & ".txt", OpenMode.Input)
 4:   FileClose(1)
 5:   Exit Function
 6:
 7: errorHandlerInCalc:
 8:   If Err.Number <> 53 Then
 9:     Err.Raise(Err.Number)
10:   Else
11:     Err.Raise(1234)
12:   End If
13: End Function
14:
15: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
16:   On Error GoTo errorHandlerInMain
17:   Trace.WriteLine(GetData("app_define_data"))
18:   Exit Sub
19:
20: errorHandlerInMain:
21:   If Err.Number <> 1234 Then
22:     Err.Raise(Err.Number)
23:   Else
24:     MsgBox("内部パラメータエラーです。")
25:   End If
26: End Sub
リスト9-57 リスト9-55をVB.NETで書き換えたプログラム

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

●図9-58 リスト9-57の実行結果

 これを、構造化例外処理を用いて書き直すと以下のようになる(リスト9-59)。

 1: Class InternalParameterErrorException
 2:   Inherits Exception
 3:   Public Sub New(ByVal msg As String, ByVal innerException As Exception)
 4:     MyBase.New(msg, innerException)
 5:   End Sub
 6: End Class
 7:
 8: Private Function GetData(ByVal filename As String)
 9:   Try
10:     FileOpen(1, "c:\" & filename & ".txt", OpenMode.Input)
11:     FileClose(1)
12:     Return "ファイルから読み出したつもりのダミーデータ"
13:   Catch ex As System.IO.FileNotFoundException
14:     Throw New InternalParameterErrorException("内部パラメータエラーです。", ex)
15:   End Try
16: End Function
17:
18: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
19:   Try
20:     Trace.WriteLine(GetData("app_define_data"))
21:   Catch ex As InternalParameterErrorException
22:     MsgBox(ex.Message & " (" & ex.InnerException.Message & ")")
23:   End Try
24: End Sub
リスト9-59 リスト9-57を、構造化例外処理を用いて記述したプログラム

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

●図9-60 リスト9-59の実行結果

 ここで結果を見比べて欲しい。構造化例外処理を使った例では、具体的に見つからなかったファイルのファイル名も表示されている。これは例外オブジェクトのInnerExceptionプロパティを利用することで実現されている。InnerExceptionプロパティは、ある例外を発生させる原因となった別の例外についての情報を保持することができる。つまり、1つの例外に対応する例外オブジェクトは1つに限られることはなく、その例外の原因となった例外に対応するオブジェクトも保持し続けることができる。もちろん、原因の例外オブジェクトにもInnerExceptionプロパティがあるので、保持できる例外の個数は2個に限定されない。

 これに対応するために、3〜5行目のコストラクタは、innerExceptionを引数に持つ形で実装し、4行目でExceptionクラスのコンストラクタにもinnerExceptionの情報を引き渡している。そして、14行目で例外を再発生させる際、引数に原因となる例外オブジェクトを指定する。この情報は、21行目でキャッチされるときに引き継がれており、22行目のようにex.InnerExceptionとすることで参照できる。つまり、ex.Messageは例外を説明する文字列を持ち、ex.InnerException.Messageは原因となった例外を説明する文字列を持つ。

 非構造化例外処理で使用されるErrオブジェクトは、システム内に1個しか存在しないため、新しいエラーが発生すると内容が上書きされてしまうのに対して、構造化例外処理の例外オブジェクトは複数のオブジェクトが存在できるので、このような使い方もできるのである。


 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