例外 (exception)

例外の捕捉

try
{
    //
}
catch (Exception e)
{
    //
}
finally
{
    //
}
try-catch-finally (C# リファレンス) | MSDN

catch

継承関係にある複数の型で捕捉するときは、派生型を先に記述する必要があります。この問題は「前の catch 句はこれ、またはスーパー型 ('***') の例外のすべてを既にキャッチしました。」としてCS0160のエラーで検出できます。

条件に一致する場合のみ捕捉 (when)

try
{
    throw new Exception("B");
}
catch (Exception e) when (e.Message == "A") // 条件が一致しないため、捕捉されない
{
}
catch (Exception e) when (e.Message == "B") // 捕捉される
{
}
コンテキスト キーワード when - C# リファレンス | Microsoft Learn

複数の型の例外を捕捉

  • それらの型の基本クラスに、whenで対象とする型を制限して捕捉する
    catch (Exception e) when (e is MyException1 || e is MyException2)
    {
    }
    
  • それらの型の基本クラスで捕捉し、対象とする型でなければ再スローする
    catch (Exception e)
    {
        if (e is MyException1 || e is MyException2)
        {
        }
        else
        {
            throw;
        }
    }
    
  • それぞれの型で捕捉し、delegateで処理を共通化する
    Action action = () => { };
    
    
    catch (MyException1) { action(); }
    catch (MyException2) { action(); }
    
c# - Catch multiple exceptions at once? - Stack Overflow

すべての型の例外を捕捉

引数を省略すると、すべての型の例外が捕捉されます。

catch
{
}

これはC++で、catch(...) {}とすることと同じです。C# 2.0以降ではCLS非準拠の例外はRuntimeWrappedExceptionに変換されるため、

catch (Exception e)
{
}

としても、すべての例外を捕捉できます。

最上位の例外ハンドラであるか、捕捉後に再スローするのでなければ、このようにすべての型の例外を捕捉すべきではありません。Exception and SystemException - Using Standard Exception Types | MSDN

捕捉されない例外

.NET Framework 4.0以降、CLRで予約されているメモリの外部で発生した例外は、SEH (structured exception handling / 構造化例外処理) 内のcatchでは捕捉されません。これを捕捉できるようにするには、例外が投げられるメソッドにHandleProcessCorruptedStateExceptionsAttribute属性を適用するか、構成ファイルに<legacyCorruptedStateExceptionsPolicy>を追加します。 AccessViolationException and try/catch blocks - AccessViolationException Class (System) | Microsoft Learn c# - How to handle AccessViolationException - Stack Overflow

ただしその例外に対処できないならば、捕捉しないようにします。

CSE (corrupted state exception / 破損状態例外)

finally

finally句はtry句で例外が発生しても実行されるため、try句で割り当てたリソースの解放などに使用できます。

try
{
    throw new Exception(); // ここから catchへ移動する
}
catch (Exception)
{
    // 例外に対する処理

    // ここから finallyへ移動する
}
finally
{
    // finally句の、次の処理へ移動する
}

// ここも処理される
// 例外は捕捉されたため、呼び出し元へ伝播しない

例外に関与しないならば、catch句は省略できます。しかしこのときリソースの解放が目的ならば、try-finallyを用いずusingステートメントで記述すべきです。

try
{
    throw new Exception(); // finallyへ移動する
}
finally
{
    // 例外は捕捉されていないため、呼び出し元へ伝播する
}

// ここには到達しない

通常finally句は、制御がtry句を離れるときに実行されます。それにはbreak、continue、goto、returnも含まれ、例外とは無関係に実行されます。

try
{
    return; // finallyへ移動する
}
finally
{
    // このブロックを抜けると、メソッドを抜ける
}

// ここには到達しない

finally句で例外を投げると、そのtry句から投げられた例外は捕捉できなくなります。

try
{
    try
    {
        throw new MyException1();
    }
    finally
    {
        throw new MyException2();
    }
}
catch (MyException1) { } // これは捕捉されない
catch (MyException2) { } // これは捕捉される

例外が捕捉されたときにはfinally句は確実に実行されますが、捕捉されずアプリケーションが終了するときには、コンピューターの設定に依存します。 try-finally (C# リファレンス) | Microsoft Learn Finally文が実行されないケースはあるか? - .NET Tips (VB.NET,C#...)

例外を捕捉しなかった場合

例外を捕捉しないと、アプリケーションは終了させられます。

コンソール アプリケーション

コンソールに次のように出力され、

ハンドルされていない例外: System.Exception: MESSAGE
   場所 sample.Program.Main(String[] args) 場所 C:\Program.cs:行 10

ダイアログボックスで、「AssemblyTitle は動作を停止しました」「問題が発生したため、プログラムが正しく動作しなくなりました。プログラムは閉じられ、解決策がある場合は Windows から通知されます。」のように通知されます。

フォーム アプリケーション

ダイアログボックスで、「アプリケーションのコンポーネントで、ハンドルされていない例外が発生しました。[続行] をクリックすると、アプリケーションはこのエラーを無視し、続行しようとします。[終了] をクリックすると、アプリケーションは直ちに終了します。」のように通知され、例外のメッセージもそこに表示されます。

メインスレッド以外からの例外

メインスレッド以外から例外が投げられた場合はこのような詳細はなく、ただ「AssemblyTitle は動作を停止しました」と表示されるのみです。この場合にはイベント ビューアーでソースが[Application Error]のイベントを探すことで、例外の詳細を確認できます。

捕捉されなかった例外を捕捉する方法

static void Main()
{
    // メインスレッドで、発生した例外
    Application.ThreadException += (object sender, System.Threading.ThreadExceptionEventArgs e) =>
    {
        Console.Write(e.Exception.Message);
    };

    // メインスレッド以外で、発生した例外
    AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) =>
    {
        Exception exception = e.ExceptionObject as Exception;
        Console.Write(exception.Message);
    };

    Application.Run(new Form1());
}
.NET TIPS:適切に処理されなかった例外をキャッチするには? - @IT 一色政彦 (2005/07/01)

例外を捕捉できない場合

デバッグ時に例外を捕捉できない

catch句があるにもかかわらず「型 '***' の例外が *** で発生しましたが、マネージとネイティブの境界の前でハンドルされませんでした」として捕捉できないときには、デバッグの設定で[例外が AppDomain またはマネージ/ネイティブの境界を越える場合にブレークする (マネージのみ)]のチェックを外します。

デリゲートで捕捉できない

例外はスタックの上位へ伝わるため、その経路にない位置では捕捉できません。

Action action = null;
try
{
    // ここではデリゲートがインスタンス化されるだけで呼び出されず、例外は投げられない
    action = () =>
    {
        throw new Exception();
    };
}
catch (Exception)
{ // ここでは捕捉されない
}


try
{
    action(); // ここでデリゲートが呼び出され、そこから例外が投げられる
}
catch (Exception)
{ // ここで捕捉される
}

例外のスロー

ExceptionやSystemExceptionをスローしないようにし、適当なクラスが存在しないならば新たに定義してスローします。Exception and SystemException - Using Standard Exception Types | MSDN

ユーザー定義の例外を作成する方法

Exceptionまたはその派生クラスを継承してクラスを作成します。

ApplicationExceptionを継承しないようにします。Remarks - ApplicationException Class (System) | Microsoft Learn

Exceptionを継承したならば、このクラスがISerializableインターフェイスを実装するためSerializableAttribute属性を指定します。これを指定しないと、コード分析により「ISerializable 型を SerializableAttribute に設定します」としてCA2237で警告されます。またpublicとしないと外部のコードで捕捉されたときに十分な情報を得られないため、「例外は public として設定する必要があります」としてCA1064で警告されます。

[Serializable]
public class MyException : Exception, ISerializable
{
    public MyException()
        : base()
    {
    }

    public MyException(string message)
        : base(message)
    {
    }

    public MyException(string message, Exception inner)
        : base(message, inner)
    {
    }

    // 逆シリアル化に用いられる
    protected MyException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
    }

    // カスタムのシリアル化を処理する
    [SecurityCritical]
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
    }
}
カスタム例外を実装します。 - Exception Class (System) | Microsoft Learn

再スロー (re-throw)

捕捉した例外をそのまま再度スローする場合は、throw;とだけ記述します。

catch (Exception e)
{
    throw;
}

throw;とするとException.StackTraceが維持されますが、throw e;とするとこれが更新されます。The throw statement - Exception-handling statements - throw and try, catch, finally - C# | Microsoft Learn

Exceptionクラス

すべてのExceptionの基本クラスです。

[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.None)]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.AutoDual)]
[System.Serializable]
public class Exception : System.Runtime.InteropServices._Exception, System.Runtime.Serialization.ISerializable
Exception Class (System) | Microsoft Learn

クラス階層

  • System.Object
    • System.Exception
      • System.AggregateException
      • System.ApplicationException
      • System.InvalidTimeZoneException
      • System.SystemException
      • System.TimeZoneNotFoundException

プロパティ

プロパティ 内容
IDictionary Data 例外に関する追加のユーザー定義情報を提供する、キー/値ペアのコレクション
string HelpLink この例外に関連付けられているヘルプ ファイルへのリンク
int HResult 特定の例外に割り当てられているコード化数値であるHRESULT
Exception InnerException 現在の例外の原因となるSystem.Exceptionインスタンス
string Message 現在の例外を説明するメッセージ
string Source エラーの原因となったアプリケーションまたはオブジェクトの名前
string StackTrace 呼び出し履歴で直前のフレームの文字列形式
MethodBase TargetSite 現在の例外がスローされたメソッド
プロパティ - Exception Class (System) | Microsoft Learn

Data

任意の情報を追加できます。

Exception e = new Exception();

e.Data.Add("info1", 1);
e.Data.Add("info2", "A");

throw e;

SystemExceptionクラス

システム例外の基本クラスです。

[System.Runtime.InteropServices.ComVisible(true)]
[System.Serializable]
public class SystemException : Exception
SystemException Class (System) | Microsoft Learn
Microsoft Learnから検索