C#のthrowとは?例外処理の基本・使い方・throw exとの違いを初心者向けに解説

はじめに

C#でプログラムを書いていると、エラーが発生したときに処理を止めたり、呼び出し元に問題を伝えたりしたい場面があります。そのようなときに使うのが、throwです。

throwは、C#の例外処理で使われる重要なキーワードです。読み方としては「スロー」と呼ばれ、日本語では「例外を投げる」と表現されます。

たとえば、メソッドに不正な値が渡されたとき、そのまま処理を続けると予期しない結果になることがあります。その場合、throwを使って例外を発生させることで、「この処理は正常に続けられない」ということを明確にできます。

この記事では、C#のthrowについて、初心者向けに基本構文から実践的な使い方、throwthrow exの違い、注意点までわかりやすく解説します。

1. C#のthrowとは?例外を発生させるための基本構文

1-1. throwは「例外を投げる」ためのキーワード

C#のthrowは、例外を発生させるためのキーワードです。

基本的な書き方は次のとおりです。

C#
throw new Exception();

このコードが実行されると、Exceptionという例外が発生します。例外が発生すると、通常の処理の流れは中断され、対応するcatchブロックがあればそこに処理が移ります。

メッセージを付けて例外を投げることもできます。

C#
throw new Exception("エラーが発生しました。");

このように書くことで、例外が発生した理由をメッセージとして伝えられます。

1-2. C#の例外処理におけるthrowの役割

C#の例外処理では、問題が発生したことをプログラムに伝える仕組みとして例外が使われます。

throwの役割は、その例外を明示的に発生させることです。

たとえば、次のような場面を考えてみましょう。

C#
public int Divide(int x, int y)
{
if (y == 0)
{
throw new ArgumentException("0で割ることはできません。");
}

return x / y;
}

このメソッドでは、y0の場合に割り算を実行するとエラーになります。そのため、事前にチェックしてthrowで例外を発生させています。

これにより、呼び出し元は「不正な値が渡された」と判断できます。

1-3. throwが使われる主な場面

throwは、主に次のような場面で使われます。

不正な引数が渡されたとき、メソッドが実行できない状態になったとき、まだ実装していない処理を明示したいとき、呼び出し元にエラーを伝えたいときなどです。

たとえば、引数がnullであってはならない場合、次のように書けます。

C#
public void PrintName(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}

Console.WriteLine(name);
}

namenullの場合、その後の処理を続けると問題が起きる可能性があります。そのため、早い段階で例外を投げて処理を止めています。

1-4. throwとtry-catch-finallyの関係

throwで投げられた例外は、try-catch-finallyで処理できます。

C#
try
{
throw new Exception("例外が発生しました。");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("最後に実行されます。");
}

tryブロックの中で例外が発生すると、対応するcatchブロックに処理が移ります。finallyブロックは、例外が発生してもしなくても最後に実行されます。

つまり、throwは例外を発生させる側、catchは例外を受け取って処理する側と考えるとわかりやすいです。

2. C#の例外処理の基本を初心者向けに整理

2-1. 例外とは何か

例外とは、プログラムの実行中に発生する通常とは異なる状態のことです。

たとえば、存在しないファイルを開こうとした場合、数値に変換できない文字列を変換しようとした場合、nullのオブジェクトを操作しようとした場合などに例外が発生します。

例外が発生すると、通常の処理は中断されます。適切に処理しないと、アプリケーションが停止したり、ユーザーにエラー画面が表示されたりします。

C#では、例外をオブジェクトとして扱います。多くの例外は、Exceptionクラスを継承したクラスとして定義されています。

2-2. try-catchの基本構文

例外を処理する基本構文は、try-catchです。

C#
try
{
// 例外が発生する可能性がある処理
}
catch (Exception ex)
{
// 例外が発生したときの処理
}

たとえば、文字列を数値に変換する処理で例外を処理する場合は次のようになります。

C#
try
{
int number = int.Parse("abc");
Console.WriteLine(number);
}
catch (FormatException ex)
{
Console.WriteLine("数値に変換できません。");
Console.WriteLine(ex.Message);
}

int.Parse("abc")は数値に変換できないため、FormatExceptionが発生します。その例外をcatchで受け取り、エラーメッセージを表示しています。

2-3. finallyの役割と使いどころ

finallyは、例外が発生してもしなくても必ず実行したい処理を書くために使います。

C#
try
{
Console.WriteLine("処理を開始します。");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("後片付けをします。");
}

finallyは、ファイルを閉じる、データベース接続を解放する、一時的に確保したリソースを片付ける、といった処理で使われます。

ただし、C#ではusing文やusing宣言を使ってリソースを自動解放することも多いため、必ずしもすべての後片付けをfinallyで書く必要はありません。

2-4. 例外処理を書かないとどうなるのか

例外処理を書かずに例外が発生した場合、その例外は呼び出し元へ伝わっていきます。

どこにもcatchがなければ、最終的にプログラムが停止することがあります。

C#
int number = int.Parse("abc");
Console.WriteLine(number);

このコードでは、"abc"を整数に変換できないため例外が発生します。例外を処理していない場合、プログラムはそこで異常終了する可能性があります。

ただし、すべての場所で無理にtry-catchを書く必要はありません。例外をその場で処理すべきか、呼び出し元に任せるべきかを考えることが大切です。

2-5. エラー処理と例外処理の違い

エラー処理とは、問題が起きたときに適切に対応するための広い考え方です。その中の一つが例外処理です。

たとえば、ユーザーが入力した文字列が空かどうかを確認する処理は、通常のエラー処理として書けます。

C#
if (string.IsNullOrEmpty(name))
{
Console.WriteLine("名前を入力してください。");
return;
}

一方、メソッドの前提条件が破られた場合や、処理を続行できない異常な状態では、例外処理を使うことがあります。

C#
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}

通常の入力ミスには分岐処理、想定外の異常状態には例外処理を使う、という考え方を持つと整理しやすくなります。

3. throwの基本的な使い方

3-1. throw new Exception()で例外を発生させる

もっとも基本的なthrowの使い方は、throw new Exception()です。

C#
throw new Exception();

これは、汎用的な例外であるExceptionクラスのインスタンスを作成し、それを投げる書き方です。

実際には、メッセージを付けて使うことが多いです。

C#
throw new Exception("処理中に問題が発生しました。");

ただし、Exceptionは非常に汎用的な例外クラスです。実務では、原因に合った具体的な例外クラスを使うことが推奨されます。

3-2. メッセージ付きで例外を投げる方法

例外には、エラー内容を説明するメッセージを付けられます。

C#
public void Save(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
{
throw new ArgumentException("ファイルパスが指定されていません。", nameof(filePath));
}

Console.WriteLine("保存処理を実行します。");
}

この例では、filePathが空文字や空白の場合にArgumentExceptionを投げています。

例外メッセージには、何が問題なのか、どの値が不正なのか、どう修正すればよいのかが伝わる内容を書くと、デバッグや保守がしやすくなります。

3-3. 条件分岐と組み合わせてthrowを使う例

throwは、if文などの条件分岐と組み合わせて使うことが多いです。

C#
public void SetAge(int age)
{
if (age < 0)
{
throw new ArgumentOutOfRangeException(nameof(age), "年齢は0以上で指定してください。");
}

Console.WriteLine($"年齢は{age}歳です。");
}

この例では、年齢がマイナスの場合に例外を投げています。

不正な値を早い段階でチェックすることで、後続処理で予期しないバグが発生するのを防げます。

3-4. 引数チェックでthrowを使う例

メソッドの引数チェックは、throwがよく使われる代表的な場面です。

C#
public void SendEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
{
throw new ArgumentException("メールアドレスを指定してください。", nameof(email));
}

Console.WriteLine($"{email} にメールを送信しました。");
}

引数がnullであればArgumentNullException、値の形式や範囲が不正であればArgumentExceptionArgumentOutOfRangeExceptionを使うのが一般的です。

C#
public void PrintMessage(string message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}

Console.WriteLine(message);
}

このように、引数の問題に応じて適切な例外クラスを選ぶことが大切です。

3-5. メソッド内でthrowを書くと処理はどうなるのか

メソッド内でthrowが実行されると、その時点で通常の処理は中断されます。

C#
public void Test()
{
Console.WriteLine("開始");

throw new Exception("例外が発生しました。");

Console.WriteLine("終了");
}

このコードでは、throwの後にあるConsole.WriteLine("終了");は実行されません。

throwが実行されると、処理は呼び出し元や対応するcatchブロックに移ります。そのため、throwの後に通常処理を書く場合は、到達できないコードにならないよう注意が必要です。

4. よく使う例外クラスとthrowの実践例

4-1. Exceptionクラスの基本

Exceptionクラスは、C#の例外クラスの基本となるクラスです。多くの例外クラスは、Exceptionを継承しています。

C#
throw new Exception("一般的な例外です。");

ただし、Exceptionは意味が広すぎるため、呼び出し元が原因を判断しにくくなります。

たとえば、不正な引数が原因なのか、状態が不正なのか、機能が未実装なのかがわかりません。

そのため、実務ではできるだけ具体的な例外クラスを使うことが重要です。

4-2. ArgumentExceptionを使うケース

ArgumentExceptionは、メソッドに渡された引数が不正な場合に使います。

C#
public void RegisterUser(string userName)
{
if (string.IsNullOrWhiteSpace(userName))
{
throw new ArgumentException("ユーザー名は必須です。", nameof(userName));
}

Console.WriteLine($"{userName}を登録しました。");
}

この例では、userNameが空の場合にArgumentExceptionを投げています。

引数の値が不正であることを表したい場合は、ExceptionではなくArgumentExceptionを使うと意図が明確になります。

4-3. ArgumentNullExceptionを使うケース

ArgumentNullExceptionは、引数にnullが渡された場合に使います。

C#
public void PrintUser(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}

Console.WriteLine(user.Name);
}

nullを許可しない引数にnullが渡された場合、この例外を投げることで、何が問題なのかが明確になります。

C#では、nameofを使うことで引数名を安全に指定できます。

C#
throw new ArgumentNullException(nameof(user));

文字列で"user"と直接書くよりも、名前変更時のミスを防ぎやすくなります。

4-4. InvalidOperationExceptionを使うケース

InvalidOperationExceptionは、オブジェクトの状態や処理の流れが不正な場合に使います。

C#
public class Order
{
public bool IsPaid { get; private set; }

public void Ship()
{
if (!IsPaid)
{
throw new InvalidOperationException("支払いが完了していないため発送できません。");
}

Console.WriteLine("商品を発送しました。");
}
}

この例では、支払いが完了していない状態で発送しようとした場合に例外を投げています。

引数が悪いのではなく、現在の状態では操作できない場合にInvalidOperationExceptionを使います。

4-5. NotImplementedExceptionを使うケース

NotImplementedExceptionは、まだ実装していない処理を明示するために使います。

C#
public void ExportCsv()
{
throw new NotImplementedException();
}

開発中にメソッドだけ先に用意しておき、後で実装する予定がある場合などに使われます。

ただし、本番環境にNotImplementedExceptionが残っていると、実行時に例外が発生します。開発中の目印として使い、リリース前には実装漏れがないか確認しましょう。

4-6. 自作例外クラスをthrowする方法

必要に応じて、自分で例外クラスを作成することもできます。

C#
public class PaymentFailedException : Exception
{
public PaymentFailedException(string message) : base(message)
{
}
}

作成した例外クラスは、通常の例外クラスと同じようにthrowできます。

C#
public void Pay()
{
bool success = false;

if (!success)
{
throw new PaymentFailedException("決済に失敗しました。");
}
}

自作例外を使うと、アプリケーション固有のエラーをわかりやすく表現できます。ただし、標準の例外クラスで十分に表現できる場合は、無理に自作する必要はありません。

5. throwとthrow exの違い

5-1. catch内で例外を再スローするとは

例外をcatchで受け取った後、ログ出力などを行い、もう一度同じ例外を呼び出し元に投げ直したい場合があります。

これを再スローといいます。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}

このように、catch内で例外を再び投げることで、上位の処理に例外を伝えられます。

5-2. throw;の使い方

throw;は、catchで受け取った例外をそのまま再スローするための構文です。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine("エラーをログに出力します。");
Console.WriteLine(ex.Message);

throw;
}

throw;を使うと、元の例外情報を保ったまま再スローできます。

特に重要なのは、スタックトレースが維持されることです。スタックトレースとは、例外がどこで発生し、どのメソッドを通って伝わってきたかを示す情報です。

5-3. throw ex;の使い方

throw ex;も、一見すると例外を再スローしているように見えます。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);

throw ex;
}

このコードでも例外は再び投げられます。

しかし、throw ex;は基本的に推奨されません。理由は、スタックトレースがリセットされ、元の例外発生箇所がわかりにくくなるためです。

5-4. throw exが推奨されない理由

throw ex;が推奨されない主な理由は、デバッグに必要な情報が失われる可能性があるからです。

例外が発生したとき、開発者はスタックトレースを見て原因箇所を調べます。

しかし、throw ex;を使うと、例外が最初に発生した場所ではなく、throw ex;を書いた場所が再スロー地点として扱われます。そのため、本当の原因箇所を追いにくくなります。

再スローしたい場合は、基本的にthrow;を使いましょう。

5-5. スタックトレースが失われる仕組み

たとえば、次のような処理があるとします。

C#
public void MethodA()
{
MethodB();
}

public void MethodB()
{
throw new Exception("MethodBで例外が発生しました。");
}

この例外をcatchしてthrow ex;すると、スタックトレース上ではthrow ex;を書いた場所が強調され、元の発生箇所を追いにくくなります。

C#
try
{
MethodA();
}
catch (Exception ex)
{
throw ex;
}

一方、次のようにthrow;を使えば、元の例外発生箇所の情報を保ったまま再スローできます。

C#
try
{
MethodA();
}
catch (Exception ex)
{
throw;
}

例外の原因を調査しやすくするためにも、再スローではthrow;を使うのが基本です。

5-6. 例外を再スローするときはthrow;を使うべき理由

例外を再スローするときにthrow;を使うべき理由は、元の例外情報を維持できるからです。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine("ログを出力します。");
Console.WriteLine(ex.Message);

throw;
}

この書き方であれば、ログを残しつつ、上位の処理に例外を正しく伝えられます。

初心者のうちは、catch内で同じ例外を投げ直す場合はthrow ex;ではなくthrow;と覚えておきましょう。

6. throw式の使い方

6-1. throw式とは何か

C#では、throwを文としてだけでなく、式として使える場面があります。これをthrow式と呼びます。

通常のthrow文は、次のように単独の文として書きます。

C#
throw new Exception("エラーです。");

一方、throw式は、値を返す式の中で例外を投げたい場合に使います。

たとえば、null合体演算子や条件演算子と組み合わせて使えます。

6-2. null合体演算子とthrow式

throw式は、??演算子とよく組み合わせて使われます。

C#
public void PrintName(string name)
{
string value = name ?? throw new ArgumentNullException(nameof(name));

Console.WriteLine(value);
}

このコードでは、namenullでなければその値をvalueに代入します。namenullであれば、ArgumentNullExceptionを投げます。

次のようにコンストラクターでもよく使われます。

C#
public class UserService
{
private readonly UserRepository _repository;

public UserService(UserRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
}

依存オブジェクトがnullの場合にすぐ例外を投げられるため、コードが簡潔になります。

6-3. 条件演算子とthrow式

throw式は、条件演算子とも組み合わせられます。

C#
public string GetRank(int score)
{
return score >= 0
? "OK"
: throw new ArgumentOutOfRangeException(nameof(score), "スコアは0以上で指定してください。");
}

この例では、scoreが0以上なら"OK"を返し、0未満なら例外を投げます。

ただし、条件演算子に複雑な処理を詰め込みすぎると読みづらくなります。シンプルに書ける場合だけ使うとよいでしょう。

6-4. throw文とthrow式の違い

throw文は、単独の文として例外を投げる書き方です。

C#
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}

一方、throw式は、式が必要な場所で例外を投げる書き方です。

C#
string value = name ?? throw new ArgumentNullException(nameof(name));

どちらも例外を発生させる点は同じです。

違いは、書ける場所です。throw文は通常の処理文として書き、throw式は代入式や条件式などの中で使えます。

6-5. throw式を使うとコードが読みやすくなるケース

throw式は、特にnullチェックを簡潔に書きたい場合に便利です。

C#
public User(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}

このように書くと、引数チェックと代入を一行で表現できます。

ただし、何でもthrow式にすればよいわけではありません。条件が複雑な場合や、複数のチェックが必要な場合は、通常のif文で書いたほうが読みやすくなります。

C#
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("名前を入力してください。", nameof(name));
}

読みやすさを優先して、throw文とthrow式を使い分けましょう。

7. throwを使うときの注意点

7-1. むやみにExceptionを投げない

throw new Exception()は簡単に書けますが、むやみに使うのはおすすめできません。

C#
throw new Exception("エラーです。");

このような書き方では、何が原因の例外なのかがわかりにくくなります。

引数が不正ならArgumentException、引数がnullならArgumentNullException、状態が不正ならInvalidOperationExceptionのように、原因に合った例外クラスを選びましょう。

7-2. 具体的な例外クラスを選ぶ

例外クラスを具体的にすると、呼び出し元で適切に処理しやすくなります。

C#
if (userId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(userId), "ユーザーIDは1以上で指定してください。");
}

この例では、値の範囲が不正であることが明確です。

具体的な例外を使うことで、コードを読む人にも意図が伝わりやすくなります。

7-3. 例外メッセージは原因が分かる内容にする

例外メッセージは、デバッグやログ調査で重要です。

悪い例は次のようなメッセージです。

C#
throw new Exception("エラーが発生しました。");

これだけでは、何が原因なのかわかりません。

よりよい例は次のようなメッセージです。

C#
throw new ArgumentException("メールアドレスが空です。", nameof(email));

どの値に問題があり、なぜ処理できないのかがわかるメッセージにしましょう。

7-4. 通常の分岐処理に例外を使いすぎない

例外は、通常の処理分岐の代わりとして使いすぎないようにしましょう。

たとえば、ユーザー入力が空だった場合に画面上でメッセージを表示するだけなら、通常のif文で十分なことが多いです。

C#
if (string.IsNullOrWhiteSpace(input))
{
Console.WriteLine("入力してください。");
return;
}

一方、メソッドの前提条件が破られ、処理を続けるべきではない場合にはthrowが適しています。

例外は、想定外の状態や処理を続行できない状態を表すために使うと考えるとよいでしょう。

7-5. catchして握りつぶさない

例外をcatchしたのに何もしないことを、例外を握りつぶすといいます。

C#
try
{
DoSomething();
}
catch (Exception)
{
}

このようなコードは、エラーが発生しても何も記録されず、原因調査が難しくなります。

少なくともログを出力する、ユーザーに適切なメッセージを表示する、必要であれば再スローするなどの対応をしましょう。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}

7-6. ログ出力とthrowの関係に注意する

例外をcatchしてログを出力し、その後throw;で再スローすることがあります。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine($"エラー: {ex.Message}");
throw;
}

この書き方自体は問題ありません。

ただし、複数の階層で同じ例外を何度もログ出力すると、ログが重複して読みにくくなることがあります。どの層でログを出すのか、どこで例外を処理するのかを設計しておくことが大切です。

8. throwのよくあるエラーと対処法

8-1. throwの後のコードが実行されない理由

throwが実行されると、その時点で通常の処理は中断されます。

C#
public void Test()
{
throw new Exception("エラー");

Console.WriteLine("この行は実行されません。");
}

このようなコードでは、throwの後の処理には到達できません。

対処法としては、throwを書く位置を見直すことです。例外を投げる条件をif文で限定するなど、必要な場合だけthrowされるようにしましょう。

C#
if (hasError)
{
throw new Exception("エラー");
}

Console.WriteLine("正常処理");

8-2. catchされない例外でプログラムが停止するケース

throwで投げた例外がどこにもcatchされない場合、プログラムが停止することがあります。

C#
public void Run()
{
throw new Exception("未処理の例外です。");
}

このメソッドを呼び出した側で例外処理をしていなければ、未処理例外になります。

必要に応じて、呼び出し元でtry-catchを書きます。

C#
try
{
Run();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

ただし、すべての例外をその場で処理する必要はありません。アプリケーション全体で共通の例外処理を用意する場合もあります。

8-3. throw exでスタックトレースが分かりにくくなるケース

初心者がよく書いてしまうのが、次のようなコードです。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
throw ex;
}

この書き方では、元の例外発生箇所が追いにくくなります。

同じ例外を再スローしたい場合は、次のように書きます。

C#
try
{
DoSomething();
}
catch (Exception)
{
throw;
}

catch内でログを出したい場合でも、再スローはthrow;にしましょう。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}

8-4. 例外型の選び方で迷ったときの判断基準

例外型に迷ったときは、原因を基準に考えると選びやすくなります。

引数がnullならArgumentNullException、引数の値が不正ならArgumentException、引数の範囲が不正ならArgumentOutOfRangeException、現在の状態では操作できないならInvalidOperationException、未実装ならNotImplementedExceptionを検討します。

たとえば、年齢に-1が渡された場合は、値の範囲が不正なのでArgumentOutOfRangeExceptionが適しています。

C#
if (age < 0)
{
throw new ArgumentOutOfRangeException(nameof(age), "年齢は0以上で指定してください。");
}

例外型を具体的にすることで、コードの意味が明確になります。

8-5. デバッグ時にthrow箇所を確認する方法

デバッグ時にthrowされた場所を確認するには、スタックトレースを見ることが重要です。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}

StackTraceには、例外が発生したメソッドや呼び出し順が表示されます。

Visual Studioなどの開発環境を使っている場合は、例外発生時にデバッガーが停止し、どの行で例外が発生したか確認できます。

throw ex;を使うと、この情報がわかりにくくなるため、再スローではthrow;を使うことが大切です。

9. throwの実務での使いどころ

9-1. 不正な引数を受け取ったとき

実務でthrowを使う代表的な場面は、不正な引数を受け取ったときです。

C#
public User GetUser(int userId)
{
if (userId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(userId), "ユーザーIDは1以上で指定してください。");
}

return new User();
}

不正な値のまま処理を続けると、後で原因がわかりにくいエラーになることがあります。

メソッドの入口で引数をチェックし、問題があればthrowすることで、バグを早期に発見しやすくなります。

9-2. 想定外の状態になったとき

オブジェクトやシステムの状態が想定外になったときにも、throwを使います。

C#
public void CompleteOrder(Order order)
{
if (order.IsCanceled)
{
throw new InvalidOperationException("キャンセル済みの注文は完了できません。");
}

order.Complete();
}

この例では、キャンセル済みの注文を完了しようとした場合に例外を投げています。

現在の状態ではその操作ができない場合は、InvalidOperationExceptionを使うと意図が伝わりやすくなります。

9-3. 未実装の処理を明示したいとき

開発中に、後で実装する予定のメソッドを作っておきたいことがあります。

C#
public void ImportData()
{
throw new NotImplementedException();
}

このように書いておくと、未実装であることが明確になります。

ただし、未実装のまま本番環境で実行されると例外が発生します。リリース前には、NotImplementedExceptionが残っていないか確認しましょう。

9-4. 呼び出し元にエラーを伝えたいとき

メソッド内で問題を処理しきれない場合、throwを使って呼び出し元にエラーを伝えます。

C#
public void LoadFile(string path)
{
if (!File.Exists(path))
{
throw new FileNotFoundException("指定されたファイルが見つかりません。", path);
}

Console.WriteLine("ファイルを読み込みます。");
}

このメソッドでは、ファイルが存在しない場合に例外を投げています。

呼び出し元は、その例外をcatchしてユーザーにメッセージを表示するなどの対応ができます。

C#
try
{
LoadFile("sample.txt");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"ファイルが見つかりません: {ex.FileName}");
}

9-5. ライブラリや共通処理でthrowを使うとき

ライブラリや共通処理では、呼び出し元がさまざまな使い方をするため、異常状態を明確に伝えることが重要です。

C#
public static int CalculateTax(int price)
{
if (price < 0)
{
throw new ArgumentOutOfRangeException(nameof(price), "価格は0以上で指定してください。");
}

return (int)(price * 0.1);
}

共通処理で不正な値をそのまま許してしまうと、アプリケーション全体に影響が広がる可能性があります。

そのため、ライブラリや共通メソッドでは、前提条件を明確にし、違反した場合は適切な例外を投げることが大切です。

10. C#のthrowに関するよくある質問

10-1. throwとreturnは何が違う?

returnは、メソッドの処理を正常に終了して値を返すために使います。

C#
return 10;

一方、throwは、例外を発生させて異常な状態を呼び出し元に伝えるために使います。

C#
throw new Exception("エラーが発生しました。");

returnは正常終了、throwは異常終了と考えるとわかりやすいです。

ただし、どちらも実行された時点で、そのメソッドの以降の通常処理は実行されません。

10-2. throwした例外はどこでcatchすべき?

throwした例外は、その例外に対して適切に対応できる場所でcatchするべきです。

たとえば、ライブラリの内部では例外を投げるだけにして、画面側でユーザー向けのメッセージを表示することがあります。

C#
try
{
service.Save();
}
catch (Exception ex)
{
Console.WriteLine("保存に失敗しました。");
Console.WriteLine(ex.Message);
}

例外を発生した場所で必ずcatchする必要はありません。むしろ、対応できない場所で無理にcatchすると、問題が隠れてしまうことがあります。

10-3. throw new Exception()は使ってもよい?

throw new Exception()は使えますが、実務ではあまり推奨されません。

C#
throw new Exception("エラー");

この書き方では、例外の意味が広すぎるため、原因を判断しにくくなります。

引数が不正ならArgumentException、状態が不正ならInvalidOperationExceptionのように、具体的な例外クラスを使うほうがよいです。

ただし、学習中に例外の動きを確認する目的で使うのであれば問題ありません。

10-4. throw;はcatchの外でも使える?

throw;は、基本的にcatchブロック内で使う構文です。

C#
try
{
DoSomething();
}
catch
{
throw;
}

catchの外でthrow;を書くことはできません。なぜなら、throw;は現在処理中の例外を再スローするための構文だからです。

新しく例外を発生させたい場合は、次のように書きます。

C#
throw new Exception("新しい例外です。");

10-5. throw exとthrow new Exceptionの違いは?

throw ex;は、catchで受け取った既存の例外オブジェクトを再び投げる書き方です。

C#
catch (Exception ex)
{
throw ex;
}

ただし、スタックトレースがわかりにくくなるため、推奨されません。

一方、throw new Exception()は、新しい例外オブジェクトを作成して投げる書き方です。

C#
throw new Exception("新しい例外です。");

既存の例外を上位に伝えたい場合は、throw;を使います。別の意味を持つ例外として包み直したい場合は、内部例外として元の例外を渡すことがあります。

C#
try
{
DoSomething();
}
catch (Exception ex)
{
throw new InvalidOperationException("処理に失敗しました。", ex);
}

このようにすると、新しい例外メッセージを付けつつ、元の例外情報も保持できます。

10-6. 初心者はまずどの書き方を覚えればよい?

初心者は、まず次の3つを覚えるとよいです。

1つ目は、不正な値に対して例外を投げる書き方です。

C#
throw new ArgumentException("引数が不正です。");

2つ目は、nullチェックで例外を投げる書き方です。

C#
throw new ArgumentNullException(nameof(value));

3つ目は、catch内で再スローするときの書き方です。

C#
throw;

特に、throw ex;ではなくthrow;を使うという点は重要です。

最初は、throw new Exception()で基本の動きを理解し、その後に具体的な例外クラスを使い分けられるようになるとよいでしょう。

まとめ

C#のthrowは、例外を発生させるためのキーワードです。処理を続けられない異常な状態や、不正な引数を受け取った場合などに使います。

基本的な書き方は、次のようになります。

C#
throw new Exception("エラーが発生しました。");

ただし、実務ではExceptionをそのまま使うよりも、原因に合った具体的な例外クラスを使うことが大切です。

引数がnullならArgumentNullException、引数の値が不正ならArgumentException、範囲が不正ならArgumentOutOfRangeException、状態が不正ならInvalidOperationExceptionを検討しましょう。

また、catch内で例外を再スローする場合は、throw ex;ではなくthrow;を使うのが基本です。

C#
try
{
DoSomething();
}
catch (Exception)
{
throw;
}

throw;を使うことで、元のスタックトレースを保ったまま例外を上位に伝えられます。

throwは便利ですが、使いすぎるとコードが読みにくくなることもあります。通常の分岐で対応できるものはif文で処理し、処理を続行できない異常な状態にはthrowを使う、という考え方が重要です。

C#の例外処理を正しく理解し、throwを適切に使うことで、エラーに強く、保守しやすいプログラムを書けるようになります。