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.Datanull の場合に、InvalidCastException を呼び出し元にスローして戻します。

catch (InvalidCastException e) when (e.Data != null)
{
    // Take some action.
}

try ブロック内では、そのブロックで宣言されている変数のみを初期化します。 そうしないと、ブロックの実行が完了する前に例外が発生する可能性があります。 たとえば、次のコードでは、変数 ntry ブロック内で初期化されています。 この変数を 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 に設定され、IsCanceledfalse に設定されます。

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 ステートメント」セクションを参照してください。

関連項目