TIPS:構文:キャッチした例外をリスローするには?[C#/VB]例外の処理時には何らかの理由で、キャッチした例外をリスローしなければならないときがある。C#やVBでこれを適切に行う方法を解説する。});連載目次キャッチした例外をリスロー(再スロー)する場合がある。キャッチしてリカバリーを試みたが成功しなかったときや、キャッチした例外を解析してみないとリカバリーできるかどうか分からないときなどに、そのキャッチした例外をcatchブロックの中から再びスローする(=リスローする)ことになる。リスローの記述は簡単なのだが、間違えやすい(特にJavaでのリスローと混同している人が多いように思われる)。本稿では、リスローの書き方を説明するとともに、よくない例も紹介する。例外をリスローするには?結論からいってしまうと、「引数なしで「throw;」(C#)/「Throw」(VB)とだけ書けば」よい。ex;」などと引数を付けて書くが、.NETでは引数を付けてはいけないのである。次に示すコードの「MethodA1」メソッドの中では正しく例外をリスローしている。Module例外をリスローするコンソールアプリの例(上:C#、下:VB)一部、行頭に行番号を付けてある。実際に試すときには、行番号は入力しないでほしい。「MethodB」メソッドの中で発生したApplicationException例外を、「MethodA1」メソッドの中でキャッチしてリスローしている。それを「Main」メソッドでキャッチして、例外のスタックトレースをコンソールに出力している。2015からのものだ。6.0]」をご覧いただきたい。TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?」で解説している。Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?」をご覧いただきたい。上のサンプルコードをデバッグ実行すると、次の画像のようにスタックトレースが出力される。例外を発生させた「MethodB」メソッドが正しくレポートされている。リスローしたときのスタックトレース出力例(上:C#、下:VB)スタックトレースの先頭に、例外が「MethodB」メソッドで発生したことがレポートされている。引数を付けてしまうと?Javaでリスローするときのようにthrowの後にキャッチした例外オブジェクトを書くと、.NETではそこで新しく例外が発生したことになる。そこでキャッチしたときまでのスタックトレースが失われてしまい、例外を引き起こした「犯人」が分からなくなってしまうのである。先ほどのサンプルコードを、次のコードのように書き換えてみよう。「MethodA2」メソッドの中で、引数付きのthrowを書いている(「MethodB」メソッドは前と同じなので省略)。Module例外を出し直しているよくない例(上:C#、下:VB)先ほどのサンプルコードの一部をこのように書き換えた。一部、行頭に行番号を付けてある。実際に試すときには、行番号は入力しないでほしい。「MethodB」メソッド(先のサンプルコードと同じなのでここでは省略)の中で発生したApplicationException例外を、「MethodA2」メソッドの中でキャッチし、今度は例外オブジェクトを付けてスローしている(Javaとは異なり、.NETではリスローにならない)。それを「Main」メソッドでキャッチして、例外のスタックトレースをコンソールに出力している。次項で述べることと比較するために、InnerExceptionの存在をチェックして出力している。上のサンプルコードをデバッグ実行すると、次の画像のようにスタックトレースが出力される。例外を発生させた「MethodB」メソッドが正しくレポートされずに、「MethodA2」メソッドで例外が発生したかのようにレポートされる。引数を付けてスローしたときのスタックトレース出力例(上:C#、下:VB)スタックトレースの先頭に、例外が「MethodA2」メソッドで発生したとレポートされている。真の例外発生箇所である「MethodB」メソッドは、どこにも出ていない。この例では「MethodA2」メソッドのtryブロックには(「MethodB」メソッドを呼び出している)1行しか書いていないので、これでも本当の例外発生箇所は「MethodB」メソッドの中にあると分かる。実際にはtryブロック内に複数行のコードを記述することも多いので、本当の例外発生箇所を特定できなくなってしまうだろう。別法:InnerExceptionを使うスタックトレースを失わないように、キャッチした例外をInnerExceptionにセットしてからスローする方法もある。これはごくたまに見かけることのある書き方であるが、別の例外に置き換えるためでなければ、catchブロックの中が複雑になるだけでメリットはない(恐らくは、先ほどの引数付きスローでスタックトレースが正しく取れなかった対策として実装したものだろう)。先ほどのサンプルコードを、次のコードのように書き換えてみよう。「MethodA3」メソッドの中で、新しく例外を作ってthrowしている(「MethodB」メソッドは前と同じなので省略)。Moduleキャッチした例外をInnerExceptionにセットしてスローするコード例(上:C#、下:VB)先ほどのサンプルコードの一部をこのように書き換えた。一部、行頭に行番号を付けてある。実際に試すときには、行番号は入力しないでほしい。このようにすれば真の例外発生箇所を特定できるが、(例外の種類を変えるという要件がない限り)リスローに比べてコードが複雑になるだけでメリットはない。「MethodB」メソッド(先のサンプルコードと同じなのでここでは省略)の中で発生したApplicationException例外を、「MethodA3」メソッドの中でキャッチし、新しく作った例外オブジェクトのInnerExceptionプロパティにそれをセットしてからスローしている。それを「Main」メソッドでキャッチして、例外のスタックトレースを(InnerExceptionの分まで)コンソールに出力している。上のサンプルコードをデバッグ実行すると、次の画像のようにスタックトレースが出力される。InnerExceptionの方に、例外を発生させた「MethodB」メソッドが正しくレポートされている。例外をInnerExceptionに入れてスローしたときのスタックトレース出力例(上:C#、下:VB)スタックトレースの先頭には、例外が「MethodA3」メソッドで発生したとレポートされている。InnerExceptionのスタックトレースを見ると、真の例外発生箇所である「MethodB」メソッドがレポートされている。このようにしてもスタックトレースを失わずに済むが、最初に示したリスローする方法に比べるとコードが複雑になっている。「MethodA3」メソッドでキャッチしたときに別の例外に置き換える必要がある場合は、キャッチした例外をこのようにしてInnerExceptionに入れるとよい。それ以外では、このような書き方をするメリットはないだろう。まとめ例外をリスローするには、引数を付けずに「throw」とだけ書く。引数を付けるとスタックトレースが失われて例外の発生箇所が分からなくなるので、注意しよう。6/VB]」を参照)。ロギングのためだけに例外をキャッチ(ロギング後にリスロー)する必要も、通常はそれほど多くないだろう。TIPS:適切に処理されなかった例外をキャッチするには?」を参照)。リスローは簡単に書けるが、しかしそれは最後の手段なのである。メールマガジンをご購読ください。申し込みページへITmediaはアイティメディア株式会社の登録商標です。