C# で例外を作成してスローする方法を確認する
- 16 分
.NET には、 System.Exception 基底クラスから派生する例外クラスの階層が用意されています。 C# アプリケーションでは、任意の例外の種類の例外を作成してスローできます。 開発者は、プロパティ値を割り当てることで、アプリケーション固有の情報を持つ例外オブジェクトをカスタマイズすることもできます。
注
このモジュールでは、例外の作成とスローと、例外オブジェクトのカスタマイズに焦点を当てます。 カスタム例外クラスの作成は、このモジュールの範囲外です。
例外オブジェクトを作成する
コード内から例外を作成してスローすることは、C# プログラミングの重要な側面です。 特定の条件、問題、またはエラーに応答して例外を生成する機能は、アプリケーションの安定性を確保するのに役立ちます。
作成する例外の種類はコーディングの問題によって異なり、可能な限り例外の目的と一致する必要があります。
たとえば、データ分析を実行する GraphData という名前のメソッドを作成しているとします。 このメソッドは、入力パラメーターとしてデータ配列を受け取ります。 このメソッドは、入力データが特定の範囲にあると想定しています。 メソッドは、予期される範囲外のデータを受け取ると、 ArgumentException型の例外を作成してスローします。 例外は、データの提供を担当するコードによって、呼び出し履歴のどこかで処理されます。
例外の作成時に使用できる一般的な例外の種類を次に示します。
ArgumentExceptionまたはArgumentNullException: メソッドまたはコンストラクターが無効な引数値または null 参照で呼び出された場合は、これらの例外型を使用します。InvalidOperationException: メソッドの操作条件が特定のメソッド呼び出しの正常な完了をサポートしていない場合は、この例外の種類を使用します。NotSupportedException: 操作または機能がサポートされていない場合は、この例外の種類を使用します。IOException: 入力/出力操作が失敗した場合は、この例外の種類を使用します。FormatException: 文字列またはデータの形式が正しくない場合は、この例外の種類を使用します。
new キーワードは、例外のインスタンスを作成するために使用されます。 たとえば、次のように、 ArgumentException 例外の種類のインスタンスを作成できます。
ArgumentException invalidArgumentException = new ArgumentException();
カスタマイズされた例外を構成してスローする
例外オブジェクトをスローするプロセスでは、例外派生クラスのインスタンスを作成し、必要に応じて例外のプロパティを構成し、 throw キーワードを使用してオブジェクトをスローします。
例外は、スローする前に、コンテキスト情報を使ってカスタマイズすると役に立つことがよくあります。 例外オブジェクト内でアプリケーション固有の情報を提供するには、そのプロパティを構成します。 たとえば、次のコードでは、カスタム invalidArgumentException プロパティを使用して Message という名前の例外オブジェクトを作成し、例外をスローします。
ArgumentException invalidArgumentException = new ArgumentException("ArgumentException: The 'GraphData' method received data outside the expected range.");
throw invalidArgumentException;
注
例外の Message プロパティは読み取り専用です。 そのため、オブジェクトをインスタンス化するときは、カスタム Message プロパティを設定する必要があります。
例外オブジェクトをカスタマイズするときは、問題とその解決方法を説明する明確なエラー メッセージを提供することが重要です。 また、ユーザーが問題を修正するのに役立つスタック トレースやエラー コードなどの追加情報を含めることもできます。
例外オブジェクトは、 throw ステートメント内に直接作成することもできます。 例えば次が挙げられます。
throw new FormatException("FormatException: Calculations in process XYZ have been cancelled due to invalid data format.");
例外をスローするときの考慮事項には、次のようなものがあります。
Messageプロパティでは、例外の理由を説明するようにします。 ただし、機密性の高い情報やセキュリティ上の問題を表す情報は、メッセージ テキストに含めてはいけません。StackTraceプロパティは、例外の発生元を追跡するためによく使用されます。 この文字列プロパティには、現在の呼び出し履歴のメソッドの名前と、例外に関連付けられている各メソッドのファイル名と行番号が含まれます。StackTraceオブジェクトは、throwステートメントのポイントから共通言語ランタイム (CLR) によって自動的に作成されます。 例外は、スタック トレースが始まる時点からスローする必要があります。
例外をスローするタイミング
意図した目的を達成できないときは常に、メソッドから例外をスローするようにします。 スローされる例外は、エラー条件に適合する使用可能な最も具体的な例外に基づいている必要があります。
開発者がビジネス プロセスを実装するアプリケーションに取り組んでいるシナリオを考えてみましょう。 ビジネス プロセスは、ユーザー入力に依存します。 その入力が想定されるデータ型と一致しない場合は、ビジネス プロセスを実装するメソッドで例外を作成してスローします。 例外オブジェクトは、プロパティ値にアプリケーション固有の情報を使用して構成できます。 次のコード サンプルは、このシナリオを示しています。
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach (string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1") && (ex is FormatException))
{
Console.WriteLine(ex.Message);
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
// completes required calculations based on userValue
// ...
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
}
}
このコード サンプルでは、最上位レベルのステートメントで BusinessProcess1 メソッドを呼び出し、ユーザーが入力した値を含む文字列配列を渡します。 BusinessProcess1 メソッドは、整数に変換できるユーザー入力値を想定しています。 メソッドは、無効な形式のデータを検出すると、カスタマイズされたFormatException プロパティを使用して、Message例外の種類のインスタンスを作成します。 次に、メソッドから例外がスローされます。 例外は、最上位レベルのステートメントで ex という名前のオブジェクトとしてキャッチされます。 ex オブジェクトのプロパティは、ユーザーに例外メッセージを表示する前に調べられます。 まず、 StackTrace プロパティを調べて、"BusinessProcess1" が含まれているかどうかを確認します。 次に、例外オブジェクト ex が FormatException型であることが確認されます。
例外の再スロー
新しい例外をスローするだけでなく、 throw を使用して、 catch コード ブロック内から例外を再スローすることもできます。 この場合、 throw は例外オペランドを受け取りません。
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// re-throw the original exception object for further handling down the call stack
throw;
}
例外を再スローすると、元の例外オブジェクトが使用されるため、例外に関する情報は失われません。 元の例外をラップする新しい例外オブジェクトを作成する場合は、元の例外を新しい例外オブジェクトのコンストラクターに引数として渡すことができます。 例えば次が挙げられます。
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred", ex);
}
"BusinessProcess1" アプリケーション シナリオでは、次の更新を検討してください。
BusinessProcess1メソッドは、追加の詳細を含むように更新されました。BusinessProcess1では、2 つの問題が発生し、各問題に対して例外を生成する必要があります。- 最上位レベルのステートメントが更新されました。 最上位レベルのステートメントで、
OperatingProcedure1メソッドが呼び出されるようになりました。OperatingProcedure1は、BusinessProcess1コード ブロック内のtryを呼び出します。 OperatingProcedure1メソッドは、例外の種類の 1 つを処理し、もう一方を部分的に処理できます。 部分的に処理された例外が処理されたら、OperatingProcedure1は元の例外を再スローする必要があります。
次のコード サンプルは、更新されたシナリオを示しています。
try
{
OperatingProcedure1();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Exiting application.");
}
static void OperatingProcedure1()
{
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach(string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1"))
{
if (ex is FormatException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Corrective action taken in OperatingProcedure1");
}
else if (ex is DivideByZeroException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Partial correction in OperatingProcedure1 - further action required");
// re-throw the original exception
throw;
}
else
{
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred - ", ex);
}
}
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
checked
{
int calculatedValue = 4 / valueEntered;
}
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
catch (DivideByZeroException)
{
DivideByZeroException unexpectedDivideByZeroException = new DivideByZeroException("DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero");
throw unexpectedDivideByZeroException;
}
}
}
更新されたサンプル コードでは、次の出力が生成されます。
FormatException: User input values in 'BusinessProcess1' must be valid integers
Corrective action taken in OperatingProcedure1
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Partial correction in OperatingProcedure1 - further action required
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Exiting application.
例外をスローするときに避ける必要があること
次の一覧は、例外をスローするときに回避すべきことを示したものです。
- 例外を使用して、通常の実行の一部としてプログラムのフローを変更しないでください。 例外を使用して、エラー状態を報告して処理します。
- 例外をスローするのではなく、戻り値またはパラメーターとして返してはなりません。
- 独自のソース コードから意図的に
System.Exception、System.SystemException、System.NullReferenceException、またはSystem.IndexOutOfRangeExceptionをスローしないでください。 - デバッグ モードではスローできてもリリース モードではスローできない例外を作成してはなりません。 開発フェーズ中にランタイム エラーを特定するには、代わりに
Debug.Assertを使用します。
注
Debug.Assertメソッドは、開発中にロジック エラーをキャッチするためのツールです。 既定では、 Debug.Assert メソッドはデバッグ ビルドでのみ機能します。 デバッグ セッションで Debug.Assert を使用して、発生しない条件を確認できます。 このメソッドは、チェックするブール条件と、条件が false場合に表示する省略可能な文字列メッセージの 2 つのパラメーターを受け取ります。 Debug.Assert は、例外をスローする代わりに使用しないでください。これは、コードの通常の実行中に例外的な状況を処理する方法です。 Debug.Assertを使用して、発生しないエラーをキャッチし、例外を使用して、プログラムの通常の実行中に発生する可能性のあるエラーを処理する必要があります。
まとめ
このユニットで覚えておく必要があるいくつかの重要な点を次に示します。
- 例外を作成してスローする場合、例外の種類は、可能な限り例外の目的と一致する必要があります。
- 例外を
throwするには、例外派生クラスのインスタンスを作成し、そのプロパティを構成してから、throwキーワードを使用します。 - 例外オブジェクトを作成するときは、ユーザーが問題を修正できるように、明確なエラー メッセージと追加情報を提供することが重要です。