C#における例外処理時の再Throwの違いについて

はじめに

システム開発において、プログラムが予期せぬ処理の状態に陥った際も異常な動作をしないよう、例外処理を必ず組み込むと思います。また、発生した例外の情報をログ等に出力し、例外発生の原因を特定するために活用することも少なくありません。
今回は例外処理時のハンドリングの違いによる挙動の差異を確認したいと思います。

開発環境

  • Visual Studio 2022
  • C#
  • コンソールアプリケーション(.NET 6.0)

確認概要

try-catch ステートメントを用いてthrow exthrowの挙動の違いについて比較を行います。

サンプルコード

下記のサンプルコードでは、コンソールに入力された文字に応じて例外を発生させ、catch句で例外の処理を分岐させます。

ファイル名 概要
Program.cs メイン処理
Util.cs メイン処理で使用するメソッド

Program.cs

using SampleApp;

public class Program
{
    /// <summary>
    /// Catch句での処理分岐用
    /// </summary>
    public static bool _IsCatchThrowEx { get; set; }

    /// <summary>
    /// メイン処理
    /// </summary>
    static void Main()
    {
        try
        {
            // 例外集約用のイベントハンドラを追加
            AppDomain.CurrentDomain.UnhandledException +=
                new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

            // 入力内容を取得
            string input = Util.GetInput();

            // 入力値に応じて例外を発生させる
            _IsCatchThrowEx = input ==  "1" ? true : false;
            Util.GenerateEx(input);

            // 結果を出力
            Util.Output(input);
        }
        catch (Exception ex)
        {
            // 入力値が1の場合
            if(_IsCatchThrowEx)
            {
                throw ex;
            }

            // 入力値が1以外の場合
            throw;
        }
    }

    /// <summary>
    /// 発生した例外情報を表示
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public static void CurrentDomain_UnhandledException(
        object sender, UnhandledExceptionEventArgs e)
    {
        Exception? ex = e.ExceptionObject as Exception;

        if (ex != null)
        {
            string msg = "例外情報" + Environment.NewLine;
            msg += "Message:" + Environment.NewLine;
            msg += ex.Message + Environment.NewLine;
            msg += "StackTrace:" + Environment.NewLine;
            msg += ex.StackTrace;

            Console.WriteLine(msg);
        }
    }
}

Util.cs

namespace SampleApp
{
    public static class Util
    {
        /// <summary>
        /// 入力内容を取得
        /// </summary>
        /// <returns></returns>
        public static string GetInput()
        {
            string msg = "1~3のいずれかの値を入力し、Enterを押下してください。";
            Console.WriteLine(msg);

            string? input = Console.ReadLine();
            input = string.IsNullOrEmpty(input) ? string.Empty : input;

            return input;
        }

        /// <summary>
        /// 入力値に応じて例外を発生させる
        /// </summary>
        /// <param name="input"></param>
        public static void GenerateEx(string input)
        {
            switch (input)
            {
                case "1":
                case "2":
                    // IndexOutOfRangeExceptionを発生させる
                    string a = string.Empty.Split(input)[1];
                    break;
                default:
                    // 何もしない
                    break;
            }
        }

        /// <summary>
        /// 結果を出力
        /// </summary>
        /// <param name="input"></param>
        public static void Output(string input)
        {
            Console.WriteLine("処理を終了します。");
        }
    }
}

差異の確認

例外の処理を Throw exThrow で行った場合のExceptionMessageStackTraceプロパティを比較します。

Throw ex の場合

例外情報
Message:
Index was outside the bounds of the array.
StackTrace:
at Program.Main() in C:\work\SampleApp\SampleApp\Program.cs:line 36
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Program.Main() in C:\work\SampleApp\SampleApp\Program.cs:line 36

Throw の場合

例外情報
Message:
Index was outside the bounds of the array.
StackTrace:
at SampleApp.Util.GenerateEx(String input) in C:\work\SampleApp\SampleApp\Util.cs:line 31
at Program.Main() in C:\work\SampleApp\SampleApp\Program.cs:line 26
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at SampleApp.Util.GenerateEx(String input) in C:\work\SampleApp\SampleApp\Util.cs:line 31
at Program.Main() in C:\work\SampleApp\SampleApp\Program.cs:line 26

出力内容に差が生じています(赤文字部分)。Throw exの場合は Program.cs のMainメソッドの 36行目が発生元となっていますが、Throwの場合は 26行目になっています。 また、Throwの場合はUtil.cs の 31行目で例外が発生した情報が出力されていますが、Throw exでは Util.cs に関する記述がありません。
このように例外の再Throwの違いにより、StackTraceに出力される内容が大きく異なります。 Throw exを使用した場合、StackTraceの情報が変更され、意図しない例外がどこで発生したかを特定することが困難になります。 また、障害発生時は迅速な原因の特定および対応が求められる場合が多いため、特別な運用がない限りはThrowを使用するほうがよいでしょう。

おわりに

運用・保守等ではログの重要性をとても感じます。多くの場合は開発期間よりも運用・保守の期間のほうが長いため、それを意識したシステム開発を行っていけたらと思います。
この記事が参考になれば幸いです。

執筆担当者プロフィール
石橋 侑樹

石橋 侑樹(日本ビジネスシステムズ株式会社)

これまで業務システムやWebアプリの開発、業務自動化等に携わってきました。サッカーが好きで休日は試合を見ていることが多いです。

担当記事一覧