例外処理ステートメント -
例外を処理するには、throw および try ステートメントを使用してください。 例外をスローするには、throw ステートメントを使用してください。 コード ブロックの実行中に発生する可能性がある例外をキャッチして処理するには、try ステートメントを使用してください。
throw ステートメント
throw ステートメントにより例外がスローされます。
if (shapeAmount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}
throw e; ステートメントでは、e の結果は System.Exception に暗黙に変換される必要があります。
組み込みの例外クラス (ArgumentOutOfRangeException や InvalidOperationException など) を使用できます。 .NET には、特定の条件で例外をスローするヘルパー メソッド (ArgumentNullException.ThrowIfNull および ArgumentException.ThrowIfNullOrEmpty) も用意されています。 System.Exception から派生する独自の例外クラスを定義することもできます。 詳細については、「例外の作成とスロー」をご覧ください。
catch ブロック内では、throw; ステートメントを使用して、catch ブロックによって処理される例外を再スローできます。
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
Note
throw; は、Exception.StackTrace プロパティに格納されている例外の元のスタック トレースを保持します。 反対に、throw e; は StackTrace の e プロパティを更新します。
例外がスローされると、共通言語ランタイム (CLR) によって、この例外を処理できる catch ブロック が検索されます。 現在実行されているメソッドにそのような catch ブロックが含まれていない場合、CLR は現在のメソッドを呼び出したメソッドなどを呼び出し履歴の上まで調べます。
catch ブロックが見つからない場合、CLR は実行中のスレッドを終了します。 詳細については、C# 言語仕様の「例外の処理方法」セクションを参照してください。
throw 式
throw は、式として使用することもできます。 これは、次に示すような多くのケースで役立ちます。
条件演算子。 次の例では、
throw式を使用して、渡された配列 ArgumentException が空の場合にargsをスローします。string first = args.Length >= 1 ? args[0] : throw new ArgumentException("Please supply at least one argument.");Null 合体演算子。 次の例では、プロパティに割り当てる文字列が
throwの場合、ArgumentNullException 式を使用してnullをスローします。public string Name { get => name; set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); }式形式のラムダまたはメソッド。 次の例では、
throw式を使用して InvalidCastException をスローし、DateTime 値への変換がサポートされていないことを示します。DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
try ステートメント
try ステートメントは、次のいずれかの形式で使用できます。try-catch - try ブロック内のコードの実行中に発生する可能性がある例外を処理する、try-finally - コントロールが try ブロックを離れたときに実行されるコードを指定する、try-catch-finally - 前の 2 つのフォームの組み合わせ。
try-catch ステートメント
コード ブロックの実行中に発生する可能性がある例外を処理するには、try-catch ステートメントを使用してください。
try ブロック内で例外が発生する可能性があるコードを配置します。 対応する ブロックで処理する例外の基本データ型を指定するには、"catch 句" を使用してください。catch
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
いくつかの catch 句を指定できます。
try
{
var result = await ProcessAsync(-3, 4, cancellationToken);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Processing is cancelled.");
}
例外が発生すると、catch 句は指定した順序で上から下に調べられます。 スローされた例外に対して最大で 1 つの catch ブロックのみが実行されます。 前の例にも示されているように、例外変数の宣言を省略し、catch 句で例外の種類のみを指定できます。 例外の種類が指定されていない catch 句は、すべての例外と一致し、存在する場合は最後の catch 句である必要があります。
キャッチされた例外を再スローする場合は、次の例に示すように、throw ステートメントを使用してください。
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
LogError(e, "Processing failed.");
throw;
}
Note
throw; は、Exception.StackTrace プロパティに格納されている例外の元のスタック トレースを保持します。 反対に、throw e; は StackTrace の e プロパティを更新します。
when 例外フィルター
例外の種類と共に、例外フィルターを指定することもできます。これは、例外をさらに調べて、対応する catch ブロックがその例外を処理するかどうかを決定します。 次の例に示すように、例外フィルターは、when キーワードに続くブール式です。
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
前の例では、例外フィルターを使用して、指定された 2 つの型の例外を処理する 1 つの catch ブロックを指定しています。
例外フィルターで区別する場合は、同じ例外の種類に対して複数 catch の句を指定できます。 これらの句の 1 つに例外フィルターがない場合があります。 このような句が存在する場合は、その例外の種類を指定する句の最後である必要があります。
catch 句に例外フィルターがある場合は、その後に表示される catch 句の例外型と同じかそれ以下の派生の例外型を指定できます。 たとえば、例外フィルターが存在する場合、catch (Exception e) 句を最後の句にする必要はありません。
例外フィルターと従来の例外処理
例外フィルターは、従来の例外処理アプローチよりも大きな利点を提供します。 主な違 いは、 例外処理ロジックが評価されるときです。
-
例外フィルター (
when): フィルター式は、スタックが巻き戻される 前に 評価されます。 つまり、元の呼び出し履歴とすべてのローカル変数は、フィルターの評価中にそのまま残ります。 -
従来の
catchブロック: スタックが巻き戻された 後 に catch ブロックが実行され、貴重なデバッグ情報が失われる可能性があります。
違いを示す比較を次に示します。
public static void DemonstrateStackUnwindingDifference()
{
var localVariable = "Important debugging info";
try
{
ProcessWithExceptionFilter(localVariable);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("filter"))
{
// Exception filter: Stack not unwound yet.
// localVariable is still accessible in debugger.
// Call stack shows original throwing ___location.
Console.WriteLine($"Caught with filter: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
try
{
ProcessWithTraditionalCatch(localVariable);
}
catch (InvalidOperationException ex)
{
// Traditional catch: Stack already unwound.
// Some debugging information may be lost.
if (ex.Message.Contains("traditional"))
{
Console.WriteLine($"Caught with if: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
else
{
throw; // Re-throws and further modifies stack trace.
}
}
}
private static void ProcessWithExceptionFilter(string context)
{
throw new InvalidOperationException($"Exception for filter demo: {context}");
}
private static void ProcessWithTraditionalCatch(string context)
{
throw new InvalidOperationException($"Exception for traditional demo: {context}");
}
例外フィルターの利点
- デバッグ エクスペリエンスの向上: フィルターが一致するまでスタックが巻き戻されないため、デバッガーはすべてのローカル変数をそのまま使用して元の障害点を表示できます。
- パフォーマンス上の利点: フィルターが一致しない場合、スタックのアンワインドと復元のオーバーヘッドなしで例外が伝達され続けます。
- クリーナー コード: 複数のフィルターで、入れ子になった if-else ステートメントを必要とせずに、同じ例外の種類の異なる条件を処理できます。
- ログと診断: 例外を処理するかどうかを決定する前に、例外の詳細を調べてログに記録できます。
public static void DemonstrateDebuggingAdvantage()
{
var contextData = new Dictionary<string, object>
{
["RequestId"] = Guid.NewGuid(),
["UserId"] = "user123",
["Timestamp"] = DateTime.Now
};
try
{
// Simulate a deep call stack.
Level1Method(contextData);
}
catch (Exception ex) when (LogAndFilter(ex, contextData))
{
// This catch block may never execute if LogAndFilter returns false.
// But LogAndFilter can examine the exception while the stack is intact.
Console.WriteLine("Exception handled after logging");
}
}
private static void Level1Method(Dictionary<string, object> context)
{
Level2Method(context);
}
private static void Level2Method(Dictionary<string, object> context)
{
Level3Method(context);
}
private static void Level3Method(Dictionary<string, object> context)
{
throw new InvalidOperationException("Error in deep call stack");
}
private static bool LogAndFilter(Exception ex, Dictionary<string, object> context)
{
// This method runs before stack unwinding.
// Full call stack and local variables are still available.
Console.WriteLine($"Exception occurred: {ex.Message}");
Console.WriteLine($"Request ID: {context["RequestId"]}");
Console.WriteLine($"Full stack trace preserved: {ex.StackTrace}");
// Return true to handle the exception, false to continue search.
return ex.Message.Contains("deep call stack");
}
例外フィルターを使用するタイミング
次の必要がある場合は、例外フィルターを使用します。
- 特定の条件またはプロパティに基づいて例外を処理します。
- デバッグのために元の呼び出し履歴を保持します。
- 例外を処理するかどうかを決定する前に、例外をログに記録するか調べます。
- コンテキストに基づいて同じ例外の種類を異なる方法で処理します。
public static void HandleFileOperations(string filePath)
{
try
{
// Simulate file operation that might fail.
ProcessFile(filePath);
}
catch (IOException ex) when (ex.Message.Contains("access denied"))
{
Console.WriteLine("File access denied. Check permissions.");
}
catch (IOException ex) when (ex.Message.Contains("not found"))
{
Console.WriteLine("File not found. Verify the path.");
}
catch (IOException ex) when (IsNetworkPath(filePath))
{
Console.WriteLine($"Network file operation failed: {ex.Message}");
}
catch (IOException)
{
Console.WriteLine("Other I/O error occurred.");
}
}
private static void ProcessFile(string filePath)
{
// Simulate different types of file exceptions.
if (filePath.Contains("denied"))
throw new IOException("File access denied");
if (filePath.Contains("missing"))
throw new IOException("File not found");
if (IsNetworkPath(filePath))
throw new IOException("Network timeout occurred");
}
private static bool IsNetworkPath(string path)
{
return path.StartsWith(@"\\") || path.StartsWith("http");
}
スタック トレースの保持
例外フィルターは、元の ex.StackTrace プロパティを保持します。
catch句で例外を処理して再スローできない場合、元のスタック情報は失われます。
when フィルターはスタックをアンワインドしないため、when フィルターがfalseされている場合、元のスタック トレースは変更されません。
例外フィルターアプローチは、デバッグ情報を保持することが問題の診断に不可欠なアプリケーションで役立ちます。
非同期および反復子メソッドの例外
次の例に示すように、非同期関数で例外が発生した場合、関数の結果を待機すると、関数の呼び出し元に伝達されます。
public static async Task Run()
{
try
{
Task<int> processing = ProcessAsync(-1);
Console.WriteLine("Launched processing.");
int result = await processing;
Console.WriteLine($"Result: {result}.");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
// Output:
// Launched processing.
// Processing failed: Input must be non-negative. (Parameter 'input')
}
private static async Task<int> ProcessAsync(int input)
{
if (input < 0)
{
throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
}
await Task.Delay(500);
return input;
}
反復子メソッドで例外が発生した場合、反復子が次の要素に進むときにのみ呼び出し元に伝達されます。
try-finally ステートメント
try-finally ステートメントでは、コントロールが finally ブロックを離れると、try ブロックが実行されます。 コントロールは、次の結果として try ブロックを離れる可能性があります。
- 正常実行、
-
ジャンプ ステートメント の実行 (つまり、
return、break、continue、goto)、または -
tryブロックからの例外の伝達。
次の例では、finally ブロックを使用して、コントロールがメソッドを離れる前にオブジェクトの状態をリセットします。
public async Task HandleRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
finally
{
Busy = false;
}
}
finally ブロックを使用して、try ブロックで使用される割り当て済みリソースをクリーン アップすることもできます。
Note
リソースの型が IDisposable または IAsyncDisposable インターフェイスを実装する場合は、using ステートメントを検討してください。
using ステートメントを使用すると、制御が using ステートメントを離れるときに、取得したリソースが確実に破棄されます。 コンパイラは、using ステートメントを try-finally ステートメントに変換します。
finally ブロックの実行は、例外のアンワインド操作がオペレーティング システムによってトリガーされるかどうかに依存します。
finally ブロックが実行されない唯一のケースは、プログラムの即時終了を伴うものです。 たとえば、このような終了は、Environment.FailFast 呼び出しや OverflowException または InvalidProgramException 例外が原因で発生する場合があります。 ほとんどのオペレーティング システムは、プロセスの停止とアンロードの一環として、リソースの適切なクリーンアップが行われます。
try-catch-finally ステートメント
try-catch-finally ブロックの実行中に発生する可能性がある例外の処理、および try ステートメントからコントロールが離れたときに実行する必要があるコードの指定の両方に try ステートメントを使います。
public async Task ProcessRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
catch (Exception e) when (e is not OperationCanceledException)
{
LogError(e, $"Failed to process request for item ID {itemId}.");
throw;
}
finally
{
Busy = false;
}
}
catch ブロックによって例外が処理されると、finally ブロックはその catch ブロックの実行後に実行されます (catch ブロックの実行中に別の例外が発生した場合でも)。
catch および finally ブロックの詳細については、それぞれ「try-catch ステートメント」および「try-finallyステートメント」を参照してください。
C# 言語仕様
詳細については、「C# 言語仕様」の次のセクションを参照してください。
関連項目
.NET