C#の throw と throw ex の動作の違いを確認する

この話、何度も説明をしているのでいい加減何か書き残した方が良いと思ったので、概要をまとめたいと思います。

C#の throw と throw ex で何が違うのかの紹介になります。

具体的には例外が発生したときに、catch節で、以下の2種類の書き方をした時の挙動の違いの説明です。

// ...前略...
catch (IOException ex)
{     
    throw ex; // (1) exを付けてthrowを使用
    
    throw; // (2) ex無し
}

ざっくりまとめると以下のようになります。

# Syntax Behavior Frequency of use
(1) throw ex; スタックトレースがリセットされる 滅多に使わない
(2) throw スタックトレースは引き継がれる 普通こっち

で、挙動の確認ですが、以下サイトさんに既にコード例があります。

tsubalog.hatenablog.com

一応こちらでもコードを書いて挙動を確認したいと思います。

確認用のコード

コードの内容を簡単にまとめると…

  • 実行・確認条件
    • Main() → FuncTop() → FuncMiddle() → FuncBottom() → ExceptionOccurd() の順にメソッドを呼ぶ
    • ExceptionOccurd() で例外が起きて Main で catch する
    • FuncTopで throw と throw ex をそれぞれ実行する
    • Mainでcatchして例外をToString()して表示される内容の違いを確認する

補足:catchで受けた例外をthrowする事を「再throw」するといいます。

// throw と throw ex の動作確認用のコード
public static void Main(string[] args)
{
    try
    {
        FuncTop(); // ↓ の関数を呼び出す
    }
    catch (InvalidOperationException ex)
    {
        Trace.WriteLine(ex);
    }

    Console.ReadLine();
}

// ↓ 呼び出される
public static void FuncTop()
{
    try
    {
        FuncMiddle(); // ↓ の関数を呼び出す
    }
    catch (InvalidOperationException ex)
    {
        throw ex; // ★★★ ここ
        //throw;
    }
}

// ↓ 呼び出される
public static void FuncMiddle() => FuncBottom();

// ↓ 呼び出される
public static void FuncBottom() => ExceptionOccurd();

// ↓ 呼び出される
public static void ExceptionOccurd() => throw new InvalidOperationException();

throw ex の出力内容

throw ex をcatchで受けてex.ToString()でコンソールへ内容を表示したときの違いです。

FuncTop() より先のスタックトレースが破棄されてthrowしたところから記録されています。

//> System.InvalidOperationException: オブジェクトの現在の状態に問題があるため、操作は有効ではありません。
//>   場所 ConsoleApp24.Program.FuncTop() 場所 D:\ConsoleApp\Program.cs:行 34
//>   場所 ConsoleApp24.Program.Main(String[] args) 場所 D:\ConsoleApp\Program.cs:行 16

throw の出力内容

こちらは throw をcatchで受けてex.ToString()した内容になります。

こちらはすべてのスタックトレースが記録されています。

//> System.InvalidOperationException: オブジェクトの現在の状態に問題があるため、操作は有効ではありません。
//>    場所 ConsoleApp.Program.ExceptionOccurd() 場所 D:\ConsoleApp\Program.cs:行 46
//>    場所 ConsoleApp.Program.FuncBottom() 場所 D:\ConsoleApp\Program.cs:行 43
//>    場所 ConsoleApp.Program.FuncMiddle() 場所 D:\ConsoleApp\Program.cs:行 40
//>    場所 ConsoleApp.Program.FuncTop() 場所 D:\ConsoleApp\Program.cs:行 35
//>    場所 ConsoleApp.Program.Main(String[] args) 場所 D:\ConsoleApp\Program.cs:行 16

まとめ

throw ex; でスタックフレームが消えている弊害(=どこで起きたか分からない)を感じるほうが多いので特別な理由がない限り「throw」を使用しましょう。(リリースしたソフトの例外がどこで起きた分かんないと滅茶苦茶困ります。