1.記述が違うとどうなる?
例外の送出内容を確認するサンプルプログラムで実験。
どちらも画面上に2つのボタンがあり、どちらのボタンも押された場合に必ず例外が発生するようにしている。メッセージボックスでその例外の内容を表示する。
※2つのボタンの処理で異なるのは例外の送出の仕方のみ(画面上のボタン→下記左のソースは「throw ex」、画面下のボタン→下記右のソースは「throw」)

青矢印・・・メソッドの呼び出し順
赤矢印・・・例外が発生する順

2.実行結果
画面
メッセージボックスの内容
System.FormatException: 入力文字列の形式が正しくありません。
場所 ExceptionThrowSample.Form1.NGPattern1()
場所 ExceptionThrowSample.Form1.BtNGPatt_Click(Object sender, EventArgs e)
System.FormatException: 入力文字列の形式が正しくありません。
場所 System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
場所 System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
場所 ExceptionThrowSample.Form1.OKPattern3()
場所 ExceptionThrowSample.Form1.OKPattern2()
場所 ExceptionThrowSample.Form1.OKPattern1()
場所 ExceptionThrowSample.Form1.BtOKPattern_Click(Object sender, EventArgs e)
「throw ex」と記述した場合
実行されたメソッドと比べて明らかに表示される情報が少ない
最初に例外が発生したのは「NGPattern1()」メソッドと表記される(実際には「NGPattern3()」なのでおかしい)
「throw」と記述した場合
標準関数からの例外も含めてメソッドの呼出履歴すべての関連メソッドが表示される
作成したプログラムの中で最初に例外が発生したのは「OKPattern3()」メソッドと表記される(OK!) ※上2つについては標準関数からの例外なので表示されていてもOK
3.実行結果が異なる理由
「throw ex」と「throw」には違いがあるため。
一見どちらも同じような意味に思えるが、ダメな方「throw ex」は、例外発生時から記述時点までに積み重なっていたメソッドの呼出履歴(StackTrace)を削除してしまう。
すなわち、例外が発生した箇所の情報が失われて、あたかもそこで初めて例外が発生したように見える挙動となってしまう。
例外を再スローする場合はエラーロギングの観点から「throw」が正しい。
また「throw ex」を使わなければならない状況は通常の開発で使うケースはほぼ無いはず。
ライブラリ開発者が、内部的に例外処理を使ったコーディングをしているが、ユーザー側に例外メッセージを見せたくない・・・とか?
☆補足:catch句の例外型の省略っていつの時代から記述できるの?
「catch」の後に例外の型を記述しない文法はVisual Studio 2005でも書けることを確認済。積極的に使用してよい。

原則、例外処理を文法に持つ言語は例外を表す基底クラスを指定できることが多い(ただしやらないほうが良い)。
例外の型すらも記述しない方法は言語によって差異有り。
- VB.NET:「Throw」のみの書き方あり。全ての例外のキャッチはC#と同様に「Catch」のみ記述。
- C++:「throw」のみの書き方あり。すべての例外をキャッチするには「catch (…)」のようにトリプルドットを記述する。
- Java:「throw」のみの書き方は存在しない。処理する例外型は列挙して縦棒で区切る(Java8以降)。VBA:[呼び出し履歴]画面を見ればわかるのでそちらを活用する
- VBScript:例外の概念がないのでErrオブジェクトを都度チェックしかない
- JavaSciprt:「throw」のみの書き方は存在しない。Javaと同じで「throw ex」と書く
- Python:C#と同じ仕様。再スローは「raise」と書く。なお、全ての例外をキャッチする「except:」という書き方があるが公式非推奨
参考
「例外処理ステートメント – throw、try-catch、try-finally、try-catch-finally」
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/exception-handling-statements
「Try…Catch…Finallyステートメント (Visual Basic)」
https://learn.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/statements/try-catch-finally-statement
例外処理の説明
文字通り例外的なエラーに対するチェックをプログラムに埋め込むと可読性が悪くなる(if文だらけになってしまう)
また、実際に例外的なエラー(=例外)が発生した場合に行うべき処理についてもメインの処理とは別に書きたい場合がある
例えば・・・
比較的ありえそうなエラーの場合は、ある場所まで復旧してユーザーに操作をやりなおしてもらいたい・・・
- 「特定位置に存在する共有ファイルのハンドルが取得できなかった」
- 「メンテナンス時間帯にシステムが起動された」
続行不可能なエラーの場合はアプリをわざとクラッシュさせて続行不可能としたい・・・
- 「処理が続行できないほどのメモリ不足に陥った」
- 「冗長性のため用意している全てのサーバが使用できなくなった」
このような時を想定し、プログラミング言語内に
必ず実行するわけではないが、想定するエラー処理を分けて記述するための文法が用意されているものがある。
これを例外処理と呼ぶ。
おおむね下記のような文法構造になっていることが多い
try
{
メイン処理
}
catch
{
例外が発生した場合の処理
}
先程の説明のように、想定する例外によってはパターンを分けたい場合がある。
その場合は例外の種類によって処理を次のように分割できる文法も存在する。
try
{
メイン処理
}
catch (例外A)
{
例外Aが発生した場合の処理
}
catch (例外B)
{
例外Bが発生した場合の処理
}
また、例外が発生した場合の処理は、メソッドの呼出元で行いたい場合がある。
そのような場合は、メソッドの呼出元に例外処理を移譲(=任せる)するため、メソッド自身が例外を再度投げる(=再スロー再送出)ことがある。
おおむね、下記のような文法となる。
c.f.)例外に対する復旧処理・ロギングなどを特定箇所・タイミングだけで行いたい
try
{
メイン処理
}
catch
{
throw
}
ある例外の場合は発生したメソッド内で行いたいが、
とある別の例外の場合はメソッドの呼出元に任せたい、場合は前述の記述を組み合わせて次のように記述することが多い
try
{
メイン処理
}
catch (例外A)//メソッド内で処理を完結させたい例外
{
例外Aが発生した場合の処理
}
catch (例外B)//メソッド内では解決できない・しない例外
{
throw
}




コメント