【C#】正しく例外を再スローする方法とExceptionDispatchInfo

C#における例外の再スローを正しく行う方法についてまとめました。

Unity2019.3.3
.Net Standard 2.0
C#の話題ですがUnityで実行確認しています

catch句で再スローする方法

まずcatch句で補足した例外を再スローすることを考えます。
まず正しい方法は以下の通りです。

using System;

public class Example
{
    private static void Test()
    {
        try
        {
            SomeMethod01();
        }
        catch (Exception e)
        {
            throw;
        }
    }

    private static void SomeMethod01() => SomeMethod02();
    private static void SomeMethod02() => SomeMethod03();
    private static void SomeMethod03() => throw new Exception();
}

上記のコードを実行すると以下のようにスタックトレース付きの例外がスローされます。

Exception: Exception of type 'System.Exception' was thrown.
Example.SomeMethod03 () (at Assets/Example.cs:43)
Example.SomeMethod02 () (at Assets/Example.cs:42)
Example.SomeMethod01 () (at Assets/Example.cs:41)
Example.Test () (at Assets/Example.cs:19)

ここで、catch句内を以下のように書いてみます(悪い例です)。

// ※※※悪い例です※※※
catch (Exception e)
{
    throw e;
}

もちろん例外はスローされるのですが、以下のようにスタックトレースが破棄された結果となってしまいます。

Exception: Exception of type 'System.Exception' was thrown.
Example.Test () (at Assets/Example.cs:18)

このようにcatch句内で再スローする場合にはスタックトレースを破棄しないよう注意が必要です。

catch句外で再スローする場合にはExceptionDispatchInfoを使う

次にcatch句で補足した例外をcatch句の外で再スローするケースを考えます。
まず以下のように保持した例外をそのままスローします(悪い例です)。

// ※※※悪い例です※※※
using System;
using UnityEditor;

public class Example
{
    private static void Test()
    {
        Exception ex = null;
        try
        {
            SomeMethod01();
        }
        catch (Exception e)
        {
            ex = e;
        }
        
        ThrowException(ex);
    }

    private static void ThrowException(Exception ex)
    {
        throw ex;
    }

    private static void SomeMethod01() => SomeMethod02();
    private static void SomeMethod02() => SomeMethod03();
    private static void SomeMethod03() => throw new Exception();
}

例外はスローされますが、以下のようにスタックトレース情報が消えてしまっています。

Exception: Exception of type 'System.Exception' was thrown.
Example.ThrowException (System.Exception ex) (at Assets/Example.cs:25)
Example.Test () (at Assets/Example.cs:20)

最初に例外がスローされたときのスタックトレースを保持したまま再スローする場合には、
以下のようにExceptionDispatchInfoを使って再スロー処理を書き替えます。

private static void ThrowException(Exception ex)
{
    ExceptionDispatchInfo.Capture(ex).Throw();
}

するとスタックトレースが保持されたまま例外を再スローできます。

Exception: Exception of type 'System.Exception' was thrown.
Example.SomeMethod03 () (at Assets/Example.cs:30)
Example.SomeMethod02 () (at Assets/Example.cs:29)
Example.SomeMethod01 () (at Assets/Example.cs:28)
Example.Test () (at Assets/Example.cs:13)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <437ba245d8404784b9fbab9b439ac908>:0)
Example.ThrowException (System.Exception ex) (at Assets/Example.cs:25)
Example.Test () (at Assets/Example.cs:20)