C#の例外の再スロー(throw;)と新しい例外のスローの違いを徹底解説!初心者でもわかる入門講座
生徒
「先生、C#でエラーが出たときにthrowを使うって聞いたんですけど、再スローと新しい例外のスローって違うんですか?」
先生
「とても大事なところですね。C#では、同じthrowでも『そのまま投げ直す』場合と『新しいエラーを投げる』場合があるんです。」
生徒
「えっ、同じように見えるのに意味が違うんですか?混乱しそうです。」
先生
「では、実際のコード例を見ながら違いをしっかり理解していきましょう。」
1. 例外処理とは?
C#における例外処理とは、プログラムの実行中に発生するエラーを安全に処理する仕組みのことです。例えば、存在しないファイルを開こうとしたり、0で割り算をした場合にエラーが発生します。このような予期しないエラーを放置するとプログラムが強制終了してしまいます。そこでtry-catchを使ってエラーを捕まえ、安全に処理を続けることができるようになります。
2. 再スロー(throw;)とは?
再スロー(throw;)とは、try-catch文で一度キャッチした例外を、そのまま呼び出し元(上位のメソッド)へ投げ直す操作のことです。C#のプログラミングにおいて、デバッグの効率を左右する非常に重要なテクニックです。
最大の特徴は、「例外が発生した場所(スタックトレース)」を完全に保存できる点にあります。どこでエラーが起きたのかという「現場の証拠」を消さずに、上位の処理にバトンタッチできるのです。
未経験の方でも分かりやすい、シンプルな計算プログラムで構造を確認してみましょう。
using System;
class Program
{
static void Main()
{
try
{
Calculate();
}
catch (Exception ex)
{
// 上位で最終的なエラー表示を行う
Console.WriteLine("【最終結果】エラーを検知しました。");
Console.WriteLine(ex.StackTrace);
}
}
static void Calculate()
{
try
{
int a = 10;
int b = 0;
int result = a / b; // ここで「0除算エラー」が発生!
}
catch (DivideByZeroException)
{
// いったんエラーを捕まえて、ログなどを出力する
Console.WriteLine("(内部ログ)0で割り算をしようとした形跡があります。");
// 証拠を維持したまま、上位のMainメソッドへ投げ直す
throw;
}
}
}
このコードを実行すると、次のような結果が得られます。
(内部ログ)0で割り算をしようとした形跡があります。
【最終結果】エラーを検知しました。
at Program.Calculate() line 25
at Program.Main() line 9
実行結果の「line 25」という部分に注目してください。throw;を使ったことで、本来エラーが起きた場所(25行目)の情報が消えずに残っています。もしこれが大規模なシステムであれば、この「行番号」という手がかりがあるおかげで、開発者は即座に修正箇所を特定できるのです。
このように、「エラーが起きた事実は記録したいけれど、処理自体は呼び出し元に任せたい」という場面で再スローは威力を発揮します。
3. 新しい例外をスローする場合
一方で、throw new Exception("メッセージ");のように「新しい例外」を作成してスローする方法があります。これは「ラップ(包み直す)」とも呼ばれますが、初心者の方が特に注意すべき点は、そのまま使うと元のエラー情報(スタックトレース)がリセットされてしまうという性質です。
例えば、システム内部の複雑なエラーをそのまま画面に出すと、利用者は混乱してしまいます。そこで、あえて元の詳細なエラーを隠し、わかりやすい独自のメッセージに作り替えて投げ直したいときにこの手法を使います。
未経験の方でも直感的に理解できるよう、配列の操作を例にコードを見てみましょう。
using System;
class Program
{
static void Main()
{
try
{
ProcessData();
}
catch (Exception ex)
{
Console.WriteLine("【Mainメソッドで検知】");
Console.WriteLine("メッセージ: " + ex.Message);
// どこでエラーが起きたか表示(新しい例外の場所が表示される)
Console.WriteLine("発生場所:\n" + ex.StackTrace);
}
}
static void ProcessData()
{
try
{
int[] numbers = { 10, 20, 30 };
// 3番目までしかない配列の「5番目」を見ようとしてエラー発生
Console.WriteLine(numbers[5]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("(内部処理)配列の範囲外アクセスを検知しました。");
// 全く新しい例外として作り直して投げる
throw new Exception("データの読み込み中に問題が発生しました。管理者に連絡してください。");
}
}
}
このプログラムを実行すると、以下のような結果になります。
(内部処理)配列の範囲外アクセスを検知しました。
【Mainメソッドで検知】
メッセージ: データの読み込み中に問題が発生しました。管理者に連絡してください。
発生場所:
at Program.ProcessData() line 32
at Program.Main() line 9
実行結果をよく見ると、本来エラーが起きた「26行目(配列アクセス)」ではなく、新しい例外を投げた「32行目」がエラーの発生源として記録されています。このように、元の原因を切り離して「ここから新しいエラーとして扱います」と宣言するのが、新しい例外のスローです。
情報を整理して伝えられる反面、本来の「犯人(エラーの真の原因)」がどこだったのか探しにくくなるという側面もあるため、使いどころが肝心です。
4. 再スローと新しいスローの違いをイメージで理解
初心者の方にも分かりやすいように例え話をしてみましょう。
- 再スローは「拾ったボールをそのまま上の人に投げ返す」イメージ。ボールについた泥(エラーの詳細)もそのまま渡ります。
- 新しいスローは「拾ったボールを捨てて、新しいボールを作って投げる」イメージ。元の泥の情報は消えてしまいます。
エラーの原因をしっかり調べたいときは再スロー、ユーザーにわかりやすいメッセージだけを伝えたいときは新しいスローを使う、と考えると理解しやすいです。
5. 実行結果の違いを確認しよう
再スローと新しいスローで、どのように実行結果が変わるのか見てみましょう。
再スローの場合:
ゼロ除算エラーを捕まえました。再スローします。
System.DivideByZeroException: Attempted to divide by zero.
at Program.Main()
新しい例外の場合:
配列の範囲外エラーを検出しました。新しい例外に変換します。
System.Exception: 配列アクセスエラーが発生しました。
at Program.Main()
再スローは元のエラー情報が保持されますが、新しいスローはメッセージだけに置き換えられることがわかります。
6. どちらを使うべきか?
実際の開発では、状況によって使い分けます。
- 再スロー:デバッグやログ出力で正確なエラー情報を残したいとき。
- 新しいスロー:ユーザーに「専門用語ではなく、わかりやすいエラーメッセージ」を伝えたいとき。
特に業務システムでは、開発者用のログには再スローで詳細を残し、ユーザーには新しい例外で「処理に失敗しました」と伝えるなど、両方を組み合わせるのが一般的です。
まとめ
ここまで、C#における例外処理のしくみや、再スローと新しい例外スローのちがいを丁寧にふりかえりながら学んできました。あらためて整理すると、再スローは元の例外情報をそのまま上位へ伝えるため、デバッグやログ解析にとても有効であり、エラーの発生箇所を正確に把握するための大切な手段になります。一方で、新しい例外のスローは、利用者に対してよりわかりやすい独自メッセージを提示したい場面で役立ち、システム利用者の視点で扱いやすいエラー表示を実現します。この二つは見た目がにていても目的が根本的にちがうため、適切に使い分けることが安定したプログラム構築につながります。とくに企業向けシステムや大規模アプリケーションでは、例外の再スローで詳細な技術情報をログへ残しつつ、画面上では新しい例外スローによるやさしいメッセージを提示し、開発者とユーザーの双方にとって扱いやすい環境を整えることが重要になります。 さらに、例外処理を正しく理解することは、プログラムの安定性を高めるだけでなく、障害発生時の原因特定や保守作業にも大きく貢献します。そのため、この章で触れた例外の再スロー、例外の再構築、例外の伝播、例外チェーン、エラーハンドリングの設計などは、基礎レベルにとどまらず応用的な場面でも非常に価値のある考え方となります。とくに再スローが保持するスタックトレースの意味や、新しい例外スローで見失われる元の例外情報の取り扱いを具体的なコードを通じて理解しておくことは、のちの開発工程で大きな差を生みます。ソフトウェア開発において例外処理は裏方のようにみえるかもしれませんが、実際にはアプリケーションの品質と信頼性を支える重要な柱のひとつであり、丁寧に向き合うほど実力がつく分野です。ここで学んだ知識を基礎として、例外フィルタ、カスタム例外クラス、ログの最適化などさらに深い世界へ進む道も開かれていきます。
再スローと新しい例外スローを確認できるサンプル
// 再スローで元のスタックトレースを保持する例
try
{
int a = 1;
int b = 0;
int r = a / b;
}
catch (DivideByZeroException ex)
{
Console.WriteLine("再スローで元の例外を保持します");
throw; // 再スロー
}
// 新しい例外を投げる例
try
{
string[] items = { "A", "B" };
var x = items[5];
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("新しい例外として投げ直します");
throw new Exception("配列の範囲外アクセスが発生しました");
}
生徒
「先生、今日の学習で再スローと新しい例外スローのちがいがだいぶ理解できました。意外と奥が深いんですね。」
先生
「そうですね。例外処理はプログラム全体の信頼性に直結する重要な要素なので、表面的な使い方ではなく、どういう目的で使い分けるかを理解することが大切なんです。」
生徒
「再スローは元のエラー情報を残すからデバッグに便利で、新しい例外はユーザー向けに見やすくするためなんですね。」
先生
「そのとおりです。実務では両方を状況に応じて使い分けます。とくにログを分析するときには再スローの情報がとても役に立ちますよ。」
生徒
「なるほど、これで例外処理の理解が一段階深まりました。もっと練習して慣れていきたいです!」
先生
「良い心がけです。今後はカスタム例外や例外フィルタも学ぶと、さらに高度なエラーハンドリングができるようになりますよ。」