C#の例外処理:try/catch/finally/throwとベストプラクティス
例外とは、プログラムの実行中に発生する問題のことです。C#の例外は、プログラム実行中に発生する例外的な状況(ゼロ除算の試みなど)への応答です。
例外は、プログラムのある部分から別の部分へ制御を移す手段を提供します。C#の例外処理は4つのキーワードに基づいて構築されています:try、catch、finally、throw。
- try - 特定の例外が有効になるコードブロックを識別。1つ以上のcatchブロックが続く
- catch - プログラムで問題を処理したい場所で例外ハンドラを使用して例外をキャッチ。
catchキーワードは例外の捕捉を示す - finally - 例外がスローされるかどうかに関わらず、指定されたステートメントセットを実行するために使用。例えばファイルを開いた場合、例外が発生しても閉じる必要がある
- throw - 問題が発生したときに例外をスロー。
throwキーワードを使用
構文
ブロックが例外を発生させると仮定すると、メソッドはtryとcatchキーワードの組み合わせで例外を捕捉します。try/catchブロックは例外を生成する可能性のあるコードの周りに配置します。try/catchブロック内のコードは保護されたコードと呼ばれ、try/catchの使用構文は以下のようになります。
try {
// 例外を引き起こす可能性のあるステートメント
} catch (ExceptionName e1) {
// エラー処理コード
} catch (ExceptionName e2) {
// エラー処理コード
} catch (ExceptionName eN) {
// エラー処理コード
} finally {
// 実行されるステートメント
}
tryブロックで異なる状況で複数の種類の例外が発生する可能性がある場合、複数のcatchステートメントを記述できます。
C#の例外クラス
C#の例外はクラスで表現されます。C#の例外クラスは主にSystem.Exceptionクラスから直接的または間接的に派生しています。System.Exceptionクラスから派生した例外クラスには、System.ApplicationExceptionとSystem.SystemExceptionクラスがあります。
- System.ApplicationException - アプリケーションプログラムによって生成される例外をサポート。プログラマーが定義する例外はこのクラスから派生させるべき
- System.SystemException - 定義済みシステム例外のすべての基底クラス
主要な定義済み例外クラス(System.SystemException派生)
| No. | 例外クラスと説明 |
|---|---|
| 1 | System.IO.IOException - I/Oエラーを処理 |
| 2 | System.IndexOutOfRangeException - 配列インデックスが範囲外のとき |
| 3 | System.ArrayTypeMismatchException - 型が配列型と不一致のとき |
| 4 | System.NullReferenceException - nullオブジェクトを参照したとき |
| 5 | System.DivideByZeroException - ゼロ除算のとき |
| 6 | System.InvalidCastException - 型キャスト時のエラー |
| 7 | System.OutOfMemoryException - 空きメモリ不足 |
| 8 | System.StackOverflowException - スタックオーバーフロー |
例外処理の例
ゼロ除算条件が発生したときに例外をスローする例です。
using System;
namespace ErrorHandlingApplication {
class DivNumbers {
int result;
DivNumbers() { result = 0; }
public void Division(int num1, int num2) {
try {
result = num1 / num2;
} catch (DivideByZeroException e) {
Console.WriteLine("Exception caught: {0}", e);
} finally {
Console.WriteLine("Result: {0}", result);
}
}
static void Main(string[] args) {
DivNumbers d = new DivNumbers();
d.Division(25, 0);
Console.ReadKey();
}
}
}
実行結果:
Exception caught: System.DivideByZeroException: Attempted to divide by zero.
Result: 0
ユーザー定義例外の作成
独自の例外を定義することもできます。ユーザー定義例外クラスはExceptionクラスから派生させます。
using System;
namespace UserDefinedException {
class TestTemperature {
static void Main(string[] args) {
Temperature temp = new Temperature();
try {
temp.ShowTemp();
} catch (TempIsZeroException e) {
Console.WriteLine("TempIsZeroException: {0}", e.Message);
}
Console.ReadKey();
}
}
}
public class TempIsZeroException : Exception {
public TempIsZeroException(string message) : base(message) { }
}
public class Temperature {
int temperature = 0;
public void ShowTemp() {
if (temperature == 0) {
throw new TempIsZeroException("Zero Temperature found");
} else {
Console.WriteLine("Temperature: {0}", temperature);
}
}
}
実行結果:
TempIsZeroException: Zero Temperature found
例外フィルター(C# 6.0以降)
whenキーワードを使用して、特定の条件下でのみcatchブロックを実行できます。
try {
// 何らかの処理
} catch (SqlException ex) when (ex.Number == 2627) {
// 主キー重複エラーのみ処理
Console.WriteLine("Duplicate key error");
} catch (SqlException ex) when (ex.Number == 547) {
// 外部キー制約違反のみ処理
Console.WriteLine("Foreign key violation");
} catch (SqlException ex) {
// その他のSQL例外
Console.WriteLine($"SQL Error: {ex.Number}");
}
例外の再スロー
catchブロックでthrowを使用して、現在の例外を上位に伝播できます。
try {
// 処理
} catch (Exception e) {
// ログ記録など
Logger.Log(e);
throw; // 元のスタックトレースを保持して再スロー
// throw e; // ❌ スタックトレースがリセットされる(非推奨)
}
例外オブジェクトのスロー
System.Exceptionクラスから直接的または間接的に派生したオブジェクトであればスローできます。
catch (Exception e) {
// 何らかの処理
throw e; // 現在の例外オブジェクトをスロー
}
usingステートメントとIDisposable(リソース確実解放)
finallyでリソースを解放する代わりに、usingステートメントを使用するとより安全かつ簡潔です。
// 従来の書き方
FileStream fs = null;
try {
fs = new FileStream("test.txt", FileMode.Open);
// 処理
} finally {
fs?.Dispose();
}
// usingステートメント(推奨)
using (FileStream fs = new FileStream("test.txt", FileMode.Open)) {
// 処理
} // 自動的にDispose()が呼ばれる
// C# 8.0以降:using宣言
using var fs = new FileStream("test.txt", FileMode.Open);
// スコープ終了時に自動Dispose
ベストプラクティス
- 例外は「例外的な状況」のみに使用し、制御フロー(通常の分岐)には使用しない
- 具体的な例外型をcatchする(
catch (Exception)は最後の手段) finallyまたはusingでリソースを確実に解放する- カスタム例外は
Exceptionから派生させ、Serializable属性を付与し、コンストラクタを標準セットで用意する - スタックトレースを保持するため再スローは
throw;を使用(throw e;ではない) - 例外メッセージは開発者向け(デバッグ用)とし、ユーザー向けメッセージは別途用意する
- パフォーマンスクリティカルなパスでは例外を避け、
TryParseパターンやResult<T>型を検討 - グローバル例外ハンドラ(ASP.NET Coreの
UseExceptionHandler等)で未処理例外を捕捉・ログ記録
例外処理のアンチパターン
- 空のcatchブロック(
catch { }) - 例外を握りつぶさない - 制御フローとしての例外使用(例:
try { return int.Parse(s); } catch { return 0; }→int.TryParseを使う) - catchして別の例外をスローする際に元の例外を
InnerExceptionとして渡さない - 大きなtryブロックで異なる種類の例外をまとめてcatchする
C言語
- PLCとDCS:適切なオートメーションシステムの選び方
- Liebherr PHS Allround:大容量生産向けの柔軟・モジュール式パレットハンドリング
- Liebherr、パレットハンドリングシステム拡充でフレキシブル生産を強化
- C# usingステートメント完全ガイド:名前空間インポート、エイリアス、静的ディレクティブ
- C言語 ファイル入出力: fopen, fprintf, fread, fseek をマスターする
- C++例外処理をマスター:try、catch、throwの実践例
- C++シグナル処理をマスター:安全な割り込み管理とクリーンなプログラム終了
- Python例外処理マスター:try、except、else、finallyを徹底解説
- Python例外処理をマスター:try、except、finally、raiseの効果的な活用
- Java例外ハンドリング完全攻略:try-catch、finally、throw、throws
- Java 9モジュールシステム:リンクタイムとカスタムランタイムイメージ