1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

throw throw exを定期的に混乱するので、違いをまとめてみた

Posted at

はじめに

毎日元気にC#の実装中、最近あまり見かけなかった例外処理を実装することになりました。
そこで、throwthrow exがどう違うのか、忘れてしまったので、備忘録ついでなので、今回この記事でまとめておくことにしました。

それぞれの特徴

さっそく、それぞれの特徴をまとめてみましょう。

throw : 元の例外情報を保持

throw ex : 例外情報がリセットされる

ざっくりわけるとこんな感じです。
ソースコードをもとに、見ていきましょう。

最初に、例外処理を比較するための実装を用意しておきます。

public class ExceptionDemo
{
    public void MethodA()
    {
        MethodB(); // 15行目
    }
    
    public void MethodB()
    {
        MethodC(); // 20行目
    }
    
    public void MethodC()
    {
        throw new InvalidOperationException("何かエラーが発生"); // 25行目 - 実際のエラー発生箇所
    }
}

パターン1: throw を使った場合

public void BadExceptionHandler()
{
    try
    {
        var demo = new ExceptionDemo();
        demo.MethodA();
    }
    catch (Exception ex)
    {
        Logger.Error($"エラーが発生: {ex.Message}");
        
        throw;
    }
}
// スタックトレース結果
InvalidOperationException: // 何かエラーが発生
   at ExceptionDemo.MethodC() in Program.cs:line 25
   at ExceptionDemo.MethodB() in Program.cs:line 20
   at ExceptionDemo.MethodA() in Program.cs:line 15
   at Program.BadExceptionHandler() in Program.cs:line 35

御覧の通りthrowを使用している場合、どこでなんのエラーが発生したか結果で一目で確認することができます。

パターン2: throw ex を使った場合

では次に、'throw ex'を使用している例を見てみましょう。

public void GoodExceptionHandler()
{
    try
    {
        var demo = new ExceptionDemo();
        demo.MethodA(); // 35行目
    }
    catch (Exception ex)
    {
        // ログ出力など
        Logger.Error($"エラーが発生: {ex.Message}");
        
        throw ex; // 例外を再作成して投げ直し
    }
}
// スタックトレース結果
InvalidOperationException: // 何かエラーが発生
   at Program.GoodExceptionHandler() in Program.cs:line 47

一方こちらでは、どこで何のエラーが発生したかが一目でわかりません。
これだと、折角例外処理を書いているのにエラー原因を特定するのにだいぶ苦労します(経験談)。

なぜこの違いが生まれるのか

この違いが生まれる理由について、少し掘り下げてみましょう。

throwの動作

throwは現在の例外オブジェクトをそのまま再スローします。
これによって、元の例外が発生した位置のスタックトレース情報が保持されるという特徴があります。

つまり、例外を最初にキャッチした場所ではなく、実際にエラーが発生した箇所の情報をデバッグ時に参照することができます。

throw exの動作

一方のthrow ex;は、ぱっと見exを再スローしているようにも見えますが、実際には新たに例外を投げなおしているというふうに、システム上みなされてしまいます。

その結果、スタックトレース情報はリセットされ、throw ex; を記述した位置が新しい例外発生地点として記録されます。

throw exのいい感じの使い方

ここまでくると、業務上での実装でthrow exの良いところなくないですかとなるので、throw exのより良い使い方も考えたいと思います。

最も簡易的な例では、例外処理に追加の情報を付与したい場合です。

catch (Exception ex)
{
    // 方法1: InnerExceptionとして元の例外を保持
    throw new CustomException("追加のエラー情報", ex);
    
    // 方法2: 元の例外にデータを追加
    ex.Data["AdditionalInfo"] = "追加情報";
    throw;
}

本番運用では具体的なエラー出力がバグ発見の大きな要因になるので、InnerExceptionなどで具体的な情報を付与することができるのは大きな利点です。

まとめ

ここまでの話をまとめると、基本的に実装するうえでは、throwを使うことが推奨されます。追加情報などを付与したい場合などは、throw exも活用できますが、PoC段階の機能実装レベルでは、throwで十分な感じがします。

ちなみにこの話は.netの設計思想にも反映されています。
.netの設計として、例外処理は通常フローとは異なる稀な状態と定義していて、例外処理を「手元で処理されるべきものではなく、むしろ「稀な事態(= exceptional)」として扱われ、一部中央集権的に管理すべき」 と設計チームの一人は唱えています。

設計思想まで深堀は今記事のスコープ外になりますが、実装中の違和感が、C#/.netがどれくらい堅牢に設計されているか、感じさせられますね。

参考リンク

Microsoft Docs: throw ステートメント
Best Practices for Exception Handling
設計者_Hejlsbergのインタビュー

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?