【C#】例外チェインでわかるExceptionクラスの要素(スタックトレースとか)

# はじめに

C#のExceptionクラスの各プロパティとメソッドの内容をExceptionを順にチェインで結んだプログラムを実行し解読します。

具体的には[C#例外クラス(Exception)](https://msdn.microsoft.com/ja-jp/library/system.exception(v=vs.110).aspx)の以下のプロパティ、メソッドの中身をコードを通して探ります。

* GetType() メソッド
* Source プロパティ
* StackTrace プロパティ
* GetBaseException() メソッド
* ToString() メソッド

# 実装

以下に3つ例外がチェインとして繋がるようにエラーハンドリングし、例外クラスの要素を出力するコードを示します。

```exception_sample.cs
using System;

namespace NDP_UE_CS
{
    // 2つの派生Exceptionをネストされた例外の
    // 動きをみるためのデモ用に定義する
    class SecondLevelException : Exception
    {
        public SecondLevelException( string message, Exception inner )
            : base( message, inner )
        { }
    }
    class ThirdLevelException : Exception
    {
        // 前Exceptionを受け取らないコンストラクタ
        public ThirdLevelException( string message )
            : base( message )
        { }

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

    class NestedExceptions
    {
        public static void Main()
        {
            Console.WriteLine(
                "\nこのプログラムは0ディビジョンを行います。このとき" +
                "異なった派生Exceptionを用いて\n" +
                "それぞれ例外を2回以上発生させます。\n");

            try
            {
                // この`Rethrow`関数は0ディビジョンを行う
                // 別関数を呼び出します。
                Rethrow( );
            }
            catch( Exception ex )
            {
                Exception current;

                Console.WriteLine(
                    "ネストされた例外をInnerExceptionプロパティを" +
                    "用いて紐解きます。\n" );

                // このコードではネストされた例外をInnerExceptionプロパティを
                // 用いて紐解きます
                current = ex;
                while( current != null )
                {
                    Console.WriteLine(string.Format("--- {0} の処理を開始 ---", current.GetType().ToString()));
                    Console.WriteLine( );

                    Console.WriteLine("Exception.Source プロパティを出力します。");
                    Console.WriteLine( current.Source.ToString( ) );
                    Console.WriteLine( );

                    Console.WriteLine("Exception.StackTrace プロパティを出力します。");
                    Console.WriteLine( current.StackTrace.ToString( ) );
                    Console.WriteLine( );

                    Console.WriteLine("Exception.GetBaseException() メソッドを出力します。");
                    Console.WriteLine( current.GetBaseException().ToString( ) );
                    Console.WriteLine( );

                    Console.WriteLine("Exception.ToString() メソッドを出力します。");
                    Console.WriteLine( current.ToString( ) );
                    Console.WriteLine( );

                    Console.WriteLine(string.Format("--- {0} の処理を終了 ---", current.GetType().ToString()));
                    Console.WriteLine( );

                    current = current.InnerException;
                }
            }
        }

        // `Rethrow`関数は`DivideBy0( )`関数からの例外をキャッチし
        // 別の例外を生成します。
        static void Rethrow()
        {
            try
            {
                DivideBy0( );
            }
            catch( Exception ex )
            {
                /*
                throw new ThirdLevelException(
                    "2番目の例外をキャッチせずに" +
                    "3番目の例外を投げます。");
                */
                throw new ThirdLevelException(
                    "2番目の例外をキャッチし" +
                    "3番目の例外を投げます。", ex);
            }
        }

        // `DivideBy0`関数は0ディビジョンを実行し
        // 2番目の例外を生成します。
        static void DivideBy0( )
        {
            try
            {
                int  zero = 0;
                int  ecks = 1 / zero;
            }
            catch( Exception ex )
            {
                throw new SecondLevelException(
                    "0ディビジョン例外をキャッチし" +
                    "2番目の例外を投げます。", ex );
            }
        }
    }
}

```

# 出力


```

このプログラムは0ディビジョンを行います。このとき異なった派生Exceptionを用いて
それぞれ例外を2回以上発生させます。

ネストされた例外をInnerExceptionプロパティを用いて紐解きます。

--- NDP_UE_CS.ThirdLevelException の処理を開始 ---

Exception.Source プロパティを出力します。
exception_sample

Exception.StackTrace プロパティを出力します。
  at NDP_UE_CS.NestedExceptions.Rethrow () [0x00016] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
  at NDP_UE_CS.NestedExceptions.Main () [0x0000a] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

Exception.GetBaseException() メソッドを出力します。
System.DivideByZeroException: Attempted to divide by zero.
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00002] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

Exception.ToString() メソッドを出力します。
NDP_UE_CS.ThirdLevelException: 2番目の例外をキャッチし3番目の例外を投げます。 ---> NDP_UE_CS.SecondLevelException: 0ディビジョン例外をキャッチし2番目の例外を投げます。 ---> System.DivideByZeroException: Attempted to divide by zero.
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00002] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
   --- End of inner exception stack trace ---
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00017] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
  at NDP_UE_CS.NestedExceptions.Rethrow () [0x00000] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
   --- End of inner exception stack trace ---
  at NDP_UE_CS.NestedExceptions.Rethrow () [0x00016] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
  at NDP_UE_CS.NestedExceptions.Main () [0x0000a] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

--- NDP_UE_CS.ThirdLevelException の処理を終了 ---

--- NDP_UE_CS.SecondLevelException の処理を開始 ---

Exception.Source プロパティを出力します。
exception_sample

Exception.StackTrace プロパティを出力します。
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00017] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
  at NDP_UE_CS.NestedExceptions.Rethrow () [0x00000] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

Exception.GetBaseException() メソッドを出力します。
System.DivideByZeroException: Attempted to divide by zero.
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00002] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

Exception.ToString() メソッドを出力します。
NDP_UE_CS.SecondLevelException: 0ディビジョン例外をキャッチし2番目の例外を投げます。 ---> System.DivideByZeroException: Attempted to divide by zero.
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00002] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
   --- End of inner exception stack trace ---
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00017] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 
  at NDP_UE_CS.NestedExceptions.Rethrow () [0x00000] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

--- NDP_UE_CS.SecondLevelException の処理を終了 ---

--- System.DivideByZeroException の処理を開始 ---

Exception.Source プロパティを出力します。
exception_sample

Exception.StackTrace プロパティを出力します。
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00002] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

Exception.GetBaseException() メソッドを出力します。
System.DivideByZeroException: Attempted to divide by zero.
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00002] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

Exception.ToString() メソッドを出力します。
System.DivideByZeroException: Attempted to divide by zero.
  at NDP_UE_CS.NestedExceptions.DivideBy0 () [0x00002] in <bb901cae5fe14264ad212a7e9f2c60f9>:0 

--- System.DivideByZeroException の処理を終了 ---


```

# 解説

上記コードには以下3つの例外が存在

0. ThirdLevelException (ユーザー定義)
0. SecondLevelException (ユーザー定義)
0. DivideByZeroException (標準例外)

ThirdLevelException(3番目の例外)が持つinnerExceptionがSecondLevelException(2番目の例外)であり、
SecondLevelExceptionが持つinnerExceptionがDivideByZeroException例外
という関係です。

### `GetType()`メソッド

3つの各例外のクラス名
`NDP_UE_CS.ThirdLevelException`, `NDP_UE_CS.SecondLevelException`, `System.DivideByZeroException`

### `Source`プロパティ

3つの各例外共通でファイル名
`exception_sample`

### `StackTrace`プロパティ

3つの各例外ごとに**例外呼び出し元の関数からキャッチされるまで**を持つ

### `GetBaseException()`メソッド

3つの各例外共通で**一番最下**の`System.DivideByZeroException`例外

### `ToString()`メソッド

自身と自身の持つInnerException例外のスタックトレースを**再帰的に**出力する。

# まとめ

* 基本的に次に新たな例外を投げるときはキャッチしている例外をコンストラクタに渡すこと

* `StackTrace`プロパティは**その例外の発生とキャッチまで**しか保持しないので、その例外だけの`StackTrace`をログに出すのは不十分

* `GetBaseException()`メソッドはその例外の根っこだけを知りたいときに利用する。(基本的にデバッグに用いるもので、ログ出力に用いるべきではなさそう)

`ToString()`メソッドはinnerExceptionを再帰的に呼び出すので十分な情報が得られる。


# 参考

* 【C#】例外チェインからスタックトレースなどの動きを追う