try-catch (C# リファレンス)
try-catch ステートメントは、try
ブロックと、それに続く 1 つ以上の catch
句で構成されます。この句にはさまざまな例外のハンドラーを指定します。
例外がスローされると、共通言語ランタイム (CLR) によって、この例外を処理する catch
ステートメントが検索されます。 現在実行されているメソッドにそのような catch
ブロックが含まれていない場合、CLR は現在のメソッドを呼び出したメソッドを検索し、呼び出し履歴の上位を検索していきます。 catch
ブロックが見つからない場合、CLR はハンドルされていない例外のメッセージをユーザーに表示し、プログラムの実行を停止します。
try
ブロックには、例外を発生させる可能性がある保護されたコードが含まれます。 このブロックは、例外がスローされるか、ブロックが正常に終了するまで実行されます。 たとえば、次の例では null
オブジェクトをキャストしようとすると、NullReferenceException 例外が発生します。
object o2 = null;
try
{
int i2 = (int)o2; // Error
}
catch
句は、引数なしで使用してすべての種類の例外をキャッチできますが、この使用方法はお勧めできません。 通常は、回復方法が判明している例外のみキャッチします。 そのため、System.Exception から派生したオブジェクト引数を必ず指定してください。 例外ハンドラーで実際には解決できない例外が誤って受け入れられないように、例外の種類はできるだけ具体的に指定する必要があります。 そのため、基本の Exception
型ではなく具体的な例外を指定します。 次に例を示します。
catch (InvalidCastException e)
{
// recover from exception
}
同一の try-catch ステートメントで、特定の catch
句を複数回使用することもできます。 この場合、catch
句は順序どおりにチェックされるため、catch
句の順序が重要になります。 例外は、特殊性の高い順にキャッチしてください。 後のブロックに到達しないような順序で catch ブロックを並べた場合、コンパイラでエラーが発生します。
処理対象の例外をフィルター処理する方法の 1 つに、catch
引数の使用があります。 また、例外フィルターを使用して例外を確認し、それを処理するかどうかを決定することもできます。 例外フィルターが false を返す場合、ハンドラーの検索が続行されます。
catch (ArgumentException e) when (e.ParamName == "…")
{
// recover from exception
}
スタックはフィルターの影響を受けないため、キャッチと再スロー (以下で説明します) には例外フィルターが適しています。 後のハンドラーでスタックをダンプすると、再スローされた最後の場所だけではなく、例外が最初に発生した場所がわかります。 例外のフィルター式の一般的な用途の 1 つにログの記録があります。 常に false を返しログも出力するフィルターを作成すれば、処理や再スローの必要なしにそのままの状態で例外をログに記録することができます。
catch
ステートメントでキャッチされた例外を再びスローするには、catch
ブロックで throw ステートメントを使用できます。 次の例では、IOException 例外からソース情報を抽出した後、親メソッドに例外をスローします。
catch (FileNotFoundException e)
{
// FileNotFoundExceptions are handled here.
}
catch (IOException e)
{
// Extract some information from this exception, and then
// throw it to the parent method.
if (e.Source != null)
Console.WriteLine("IOException source: {0}", e.Source);
throw;
}
例外をキャッチして、別の例外をスローできます。 これを行うには、次の例に示すように、キャッチする例外を内部例外として指定します。
catch (InvalidCastException e)
{
// Perform some action here, and then throw a new exception.
throw new YourCustomException("Put your error message here.", e);
}
次の例に示すように、指定した条件が true の場合に例外を再スローすることもできます。
catch (InvalidCastException e)
{
if (e.Data == null)
{
throw;
}
else
{
// Take some action.
}
}
注意
また、例外フィルターを使用すると、多くの場合、よりクリーンな方法で同様の結果を得ることができます (このドキュメントで前述したような、スタックの変更もありません)。 次の例では、呼び出し元に対して前の例と同様の動作をします。 この関数は、e.Data
が null
の場合に、InvalidCastException
を呼び出し元にスローして戻します。
catch (InvalidCastException e) when (e.Data != null)
{
// Take some action.
}
try
ブロック内では、そのブロックで宣言されている変数のみを初期化します。 そうしないと、ブロックの実行が完了する前に例外が発生する可能性があります。 たとえば、次のコードでは、変数 n
が try
ブロック内で初期化されています。 この変数を try
ブロックの外側にある Write(n)
ステートメントで使おうとすると、コンパイラ エラーが発生します。
static void Main()
{
int n;
try
{
// Do not initialize this variable here.
n = 123;
}
catch
{
}
// Error: Use of unassigned local variable 'n'.
Console.Write(n);
}
catch の詳細については、「try-catch-finally」を参照してください。
非同期メソッドの例外
非同期メソッドは async 修飾子でマークされ、通常は 1 つ以上の await 式またはステートメントが含まれます。 await 式では、await 演算子が Task または Task<TResult> に適用されます。
コントロールが非同期メソッドの await
に到達すると、メソッドの進行状況は、待機中のタスクが完了するまで中断されます。 タスクが完了すると、メソッドで実行を再開できます。 詳細については、Async および Await を使用した非同期プログラミングに関するページをご覧ください。
await
が適用される完了したタスクは、タスクを返すメソッドでハンドルされない例外が発生したことが原因で、違反状態になる場合があります。 タスクを待機すると例外がスローされます。 タスクを返す非同期処理が取り消された場合に、取り消された状態でタスクを終了することもできます。 キャンセルされたタスクを待機していると、OperationCanceledException
がスローされます。
例外をキャッチするには、try
ブロックでタスクを待機し、関連付けられている catch
ブロックで例外をキャッチします。 例については、async メソッドの例に関するセクションを参照してください。
待機中の非同期メソッドで複数の例外が発生したことが原因で、タスクが違反状態になることがあります。 たとえば、タスクは Task.WhenAll の呼び出しの結果になることがあります。 このようなタスクを待機すると、1 つの例外のみがキャッチされます。どの例外がキャッチされるかは予測できません。 例については、Task.WhenAll の例に関するセクションを参照してください。
例
例外が発生する可能性がある ProcessString
メソッドへの呼び出しを含む try
ブロックの例を次に示します。 catch
句には、メッセージを画面に表示するだけの例外ハンドラーがあります。 throw
ステートメントが ProcessString
の内側から呼び出されると、システムによって catch
ステートメントが検索され、メッセージ Exception caught
が表示されます。
class TryFinallyTest
{
static void ProcessString(string s)
{
if (s == null)
{
throw new ArgumentNullException(paramName: nameof(s), message: "parameter can't be null.");
}
}
public static void Main()
{
string s = null; // For demonstration purposes.
try
{
ProcessString(s);
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e);
}
}
}
/*
Output:
System.ArgumentNullException: Value cannot be null.
at TryFinallyTest.Main() Exception caught.
* */
2 つの catch ブロックの例
次の例では、2 種類の catch ブロックが使用され、特殊性が最も高い最初の例外がキャッチされます。
特殊性が最も低い例外をキャッチするには、ProcessString
の throw ステートメントを throw new Exception()
と置き換えることができます。
この例で最も特殊ではない catch ブロックを最初に配置すると、次のエラー メッセージが表示されます。A previous catch clause already catches all exceptions of this or a super type ('System.Exception')
(前の catch 句は、この、または super 型のすべての例外をキャッチしています ('System.Exception'))。
class ThrowTest3
{
static void ProcessString(string s)
{
if (s == null)
{
throw new ArgumentNullException(paramName: nameof(s), message: "Parameter can't be null");
}
}
public static void Main()
{
try
{
string s = null;
ProcessString(s);
}
// Most specific:
catch (ArgumentNullException e)
{
Console.WriteLine("{0} First exception caught.", e);
}
// Least specific:
catch (Exception e)
{
Console.WriteLine("{0} Second exception caught.", e);
}
}
}
/*
Output:
System.ArgumentNullException: Value cannot be null.
at Test.ThrowTest3.ProcessString(String s) ... First exception caught.
*/
非同期メソッドの例
次の例では、非同期メソッドの例外処理を示します。 非同期タスクからスローされる例外をキャッチするには、try
ブロックに await
式を配置し、catch
ブロックで例外をキャッチします。
例外処理を示すために、この例の throw new Exception
行のコメントを解除します。 タスクの IsFaulted
プロパティが True
に設定され、タスクの Exception.InnerException
プロパティが例外に設定され、例外が catch
ブロックでキャッチされます。
throw new OperationCanceledException
行のコメントを解除して、非同期処理を取り消したときに何が起こるかを示します。 タスクの IsCanceled
プロパティが true
に設定され、例外が catch
ブロックでキャッチされます。 この例に該当しない一部の条件では、タスクの IsFaulted
プロパティが true
に設定され、IsCanceled
が false
に設定されます。
public async Task DoSomethingAsync()
{
Task<string> theTask = DelayAsync();
try
{
string result = await theTask;
Debug.WriteLine("Result: " + result);
}
catch (Exception ex)
{
Debug.WriteLine("Exception Message: " + ex.Message);
}
Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
Debug.WriteLine("Task IsFaulted: " + theTask.IsFaulted);
if (theTask.Exception != null)
{
Debug.WriteLine("Task Exception Message: "
+ theTask.Exception.Message);
Debug.WriteLine("Task Inner Exception Message: "
+ theTask.Exception.InnerException.Message);
}
}
private async Task<string> DelayAsync()
{
await Task.Delay(100);
// Uncomment each of the following lines to
// demonstrate exception handling.
//throw new OperationCanceledException("canceled");
//throw new Exception("Something happened.");
return "Done";
}
// Output when no exception is thrown in the awaited method:
// Result: Done
// Task IsCanceled: False
// Task IsFaulted: False
// Output when an Exception is thrown in the awaited method:
// Exception Message: Something happened.
// Task IsCanceled: False
// Task IsFaulted: True
// Task Exception Message: One or more errors occurred.
// Task Inner Exception Message: Something happened.
// Output when a OperationCanceledException or TaskCanceledException
// is thrown in the awaited method:
// Exception Message: canceled
// Task IsCanceled: True
// Task IsFaulted: False
Task.WhenAll の例
次の例では、複数のタスクで複数の例外が発生する可能性がある例外処理について説明します。 try
ブロックは Task.WhenAll の呼び出しで返されるタスクを待機します。 WhenAll が適用される 3 つのタスクが完了すると、このタスクは完了します。
3 つのタスクでそれぞれ例外が発生します。 catch
ブロックは例外を反復処理します。この例外は、Task.WhenAll で返されたタスクの Exception.InnerExceptions
プロパティで見つかります。
public async Task DoMultipleAsync()
{
Task theTask1 = ExcAsync(info: "First Task");
Task theTask2 = ExcAsync(info: "Second Task");
Task theTask3 = ExcAsync(info: "Third Task");
Task allTasks = Task.WhenAll(theTask1, theTask2, theTask3);
try
{
await allTasks;
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex.Message);
Debug.WriteLine("Task IsFaulted: " + allTasks.IsFaulted);
foreach (var inEx in allTasks.Exception.InnerExceptions)
{
Debug.WriteLine("Task Inner Exception: " + inEx.Message);
}
}
}
private async Task ExcAsync(string info)
{
await Task.Delay(100);
throw new Exception("Error-" + info);
}
// Output:
// Exception: Error-First Task
// Task IsFaulted: True
// Task Inner Exception: Error-First Task
// Task Inner Exception: Error-Second Task
// Task Inner Exception: Error-Third Task
C# 言語仕様
詳細については、「C# 言語仕様」の「try ステートメント」セクションを参照してください。