C#のtry-catch完全ガイド|例外処理の基本・書き方・よくあるエラー対策を初心者向けに解説

はじめに

C#でプログラムを書いていると、実行中に思わぬエラーが発生することがあります。たとえば、存在しないファイルを読み込もうとしたり、文字列を数値に変換できなかったり、nullの変数にアクセスしてしまったりするケースです。

このような実行時の問題に対応するために使うのが、C#のtry-catchです。

try-catchを正しく使えるようになると、エラーが発生してもプログラムを安全に継続したり、ユーザーにわかりやすいメッセージを表示したり、ログを残して原因を調査しやすくしたりできます。

この記事では、C#のtry-catchについて、基本構文から実践的なサンプル、finallythrow、非同期処理での使い方、よくあるエラー対策まで初心者向けにわかりやすく解説します。

1. C#のtry-catchとは?例外処理の役割を初心者向けに解説

C#のtry-catchとは、プログラムの実行中に発生する例外を捕まえて処理するための構文です。

例外とは、プログラムが通常どおり処理を続けられない状態になったときに発生する特別なエラーのことです。C#では、例外が発生すると、そのままではプログラムが停止してしまう場合があります。

そこでtry-catchを使うことで、例外が起きそうな処理をtryブロックに書き、例外が発生したときの対応をcatchブロックに書くことができます。

1-1. try-catchは何のために使うのか

try-catchは、エラーが発生する可能性のある処理を安全に実行するために使います。

たとえば、次のような処理では例外が発生する可能性があります。

C#
int result = 10 / 0;

0で割り算をすると、DivideByZeroExceptionという例外が発生します。try-catchを使わない場合、プログラムはそこで停止してしまいます。

try-catchを使うと、例外が発生した場合でも次のように対応できます。

C#
try
{
int result = 10 / 0;
}
catch (DivideByZeroException)
{
Console.WriteLine("0で割ることはできません。");
}

このように、エラーが発生したときにプログラムをいきなり終了させるのではなく、適切なメッセージを表示したり、代替処理を行ったりするためにtry-catchを使います。

1-2. 例外処理と通常のエラー処理の違い

通常のエラー処理では、if文などを使って事前に問題を防ぎます。

C#
int number = 0;

if (number != 0)
{
Console.WriteLine(10 / number);
}
else
{
Console.WriteLine("0では割れません。");
}

一方、例外処理は、実行してみないと成功するかわからない処理に対して使われます。

たとえば、ファイル読み込みでは、次のような問題が起きる可能性があります。

C#
string text = File.ReadAllText("sample.txt");

この処理では、ファイルが存在しない、アクセス権限がない、他のアプリが使用中であるなど、さまざまな理由で失敗する可能性があります。

このような予測しきれない実行時エラーに対応するのが例外処理です。

1-3. C#で例外処理が必要になる代表的な場面

C#でtry-catchがよく使われる場面には、次のようなものがあります。

ファイルの読み書きでは、ファイルが存在しない、パスが間違っている、アクセス権限がないといった例外が発生する可能性があります。

数値変換では、ユーザーが入力した文字列をint.Parseなどで数値に変換するとき、数字ではない文字が含まれているとFormatExceptionが発生します。

データベース接続では、接続先サーバーにアクセスできない、SQLに誤りがある、認証に失敗するなどの問題が発生することがあります。

API通信では、ネットワーク障害、タイムアウト、サーバーエラーなどにより処理が失敗する場合があります。

また、配列やリストの範囲外アクセス、null参照、ゼロ除算なども、C#でよく見かける例外です。

1-4. try-catchを使わないとプログラムはどうなるのか

try-catchを使わずに例外が発生すると、基本的にはその処理が中断されます。コンソールアプリであれば、エラーメッセージが表示されてプログラムが終了することがあります。

たとえば、次のコードを実行します。

C#
int x = 10;
int y = 0;
int result = x / y;

Console.WriteLine("処理が完了しました。");

この場合、x / yの部分でDivideByZeroExceptionが発生するため、最後のConsole.WriteLineは実行されません。

しかし、try-catchを使えば、例外を受け止めて処理を続けることができます。

C#
try
{
int x = 10;
int y = 0;
int result = x / y;
}
catch (DivideByZeroException)
{
Console.WriteLine("計算中にエラーが発生しました。");
}

Console.WriteLine("処理を続行します。");

このように、try-catchはプログラムの異常終了を防ぐために重要な役割を持っています。

2. C#のtry-catchの基本構文と書き方

C#のtry-catchは、基本的に次の形で書きます。

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

実際の開発では、どのような例外が発生したのかを知るために、catchに例外型を指定することが多いです。

C#
try
{
// 例外が発生する可能性のある処理
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

2-1. tryブロックの役割

tryブロックには、例外が発生する可能性のある処理を書きます。

C#
try
{
string text = File.ReadAllText("sample.txt");
Console.WriteLine(text);
}

この例では、sample.txtというファイルを読み込もうとしています。ファイルが存在しない場合や読み取り権限がない場合には例外が発生します。

tryブロック内で例外が発生すると、それ以降のtry内の処理は実行されず、対応するcatchブロックに処理が移ります。

2-2. catchブロックの役割

catchブロックには、例外が発生したときに実行したい処理を書きます。

C#
catch (Exception ex)
{
Console.WriteLine("エラーが発生しました。");
Console.WriteLine(ex.Message);
}

exには、発生した例外の情報が入っています。ex.Messageを使うと、例外の内容を表すメッセージを取得できます。

ただし、ユーザー向け画面にex.Messageをそのまま表示するのは注意が必要です。内部情報やファイルパスなど、見せるべきでない情報が含まれる場合があるためです。

2-3. Exception型を使った基本的な書き方

C#の多くの例外は、Exceptionクラスを基底クラスとして持っています。そのため、catch (Exception ex)と書くと、多くの例外をまとめて捕まえることができます。

C#
try
{
int number = int.Parse("abc");
}
catch (Exception ex)
{
Console.WriteLine("例外が発生しました。");
Console.WriteLine(ex.Message);
}

このコードでは、"abc"を整数に変換しようとしているため、FormatExceptionが発生します。しかし、catch (Exception ex)で受け取れるため、プログラムの異常終了を防げます。

ただし、すべてをExceptionでまとめて処理するのは便利な反面、どの例外が発生したのかに応じた細かい対応がしにくくなります。実務では、できるだけ具体的な例外型を指定することが大切です。

2-4. 初心者向けの最小サンプルコード

まずは、もっともシンプルなtry-catchの例を見てみましょう。

C#
try
{
int result = 10 / 0;
Console.WriteLine(result);
}
catch
{
Console.WriteLine("エラーが発生しました。");
}

このコードでは、10 / 0の部分で例外が発生します。そのため、Console.WriteLine(result);は実行されず、catchブロックの中に処理が移ります。

結果として、画面には次のように表示されます。

C#
エラーが発生しました。

ただし、原因を確認しやすくするためには、次のように例外情報を受け取る書き方がおすすめです。

C#
try
{
int result = 10 / 0;
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine("エラーが発生しました。");
Console.WriteLine(ex.Message);
}

2-5. try-catch内の処理の流れをステップで理解する

try-catchの処理の流れは、次のように考えると理解しやすいです。

まず、プログラムはtryブロックの先頭から順番に処理を実行します。例外が発生しなければ、catchブロックは実行されません。

例外が発生した場合は、その時点でtryブロックの残りの処理をスキップします。そして、発生した例外に対応するcatchブロックを探します。

対応するcatchが見つかれば、その中の処理が実行されます。catchの処理が終わると、try-catchの後ろにある処理へ進みます。

C#
try
{
Console.WriteLine("1. try開始");

int result = 10 / 0;

Console.WriteLine("2. 計算完了");
}
catch (Exception)
{
Console.WriteLine("3. catch実行");
}

Console.WriteLine("4. try-catchの後の処理");

この場合、10 / 0で例外が発生するため、2. 計算完了は表示されません。

実行結果は次のようになります。

C#
1. try開始
3. catch実行
4. try-catchの後の処理

3. C#のtry-catchを使った実践サンプル

ここからは、C#のtry-catchを使った実践的なサンプルを紹介します。

初心者のうちは、どのような場面でtry-catchを書くべきか迷いやすいですが、まずはよくある例外パターンを覚えると理解しやすくなります。

3-1. ゼロ除算をtry-catchで処理する例

整数を0で割ると、DivideByZeroExceptionが発生します。

C#
try
{
int x = 100;
int y = 0;

int result = x / y;

Console.WriteLine($"計算結果: {result}");
}
catch (DivideByZeroException)
{
Console.WriteLine("0で割ることはできません。");
}

このコードでは、x / yで例外が発生し、catchブロックに処理が移ります。

ゼロ除算は、実際にはtry-catchで処理するよりも、事前にif文で防げる場合が多いです。

C#
if (y != 0)
{
int result = x / y;
Console.WriteLine(result);
}
else
{
Console.WriteLine("割る数には0以外を指定してください。");
}

このように、事前にチェックできるものはif文で防ぎ、予測しきれないものはtry-catchで処理するのが基本です。

3-2. ファイル読み込みエラーをtry-catchで処理する例

ファイル読み込みは、try-catchがよく使われる代表的な場面です。

C#
try
{
string text = File.ReadAllText("data.txt");
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("指定されたファイルが見つかりません。");
}
catch (IOException ex)
{
Console.WriteLine("ファイルの読み込み中にエラーが発生しました。");
Console.WriteLine(ex.Message);
}

FileNotFoundExceptionは、ファイルが存在しない場合に発生する例外です。IOExceptionは、入出力処理全般で発生する例外です。

ファイル操作では、パスの間違い、権限不足、ファイルのロックなどさまざまな問題が起きるため、例外処理を入れておくと安全です。

3-3. null参照エラーをtry-catchで処理する例

C#でよくある例外のひとつがNullReferenceExceptionです。これは、nullの変数に対してプロパティやメソッドを呼び出そうとしたときに発生します。

C#
try
{
string name = null;
Console.WriteLine(name.Length);
}
catch (NullReferenceException)
{
Console.WriteLine("nullの値を参照しようとしました。");
}

ただし、NullReferenceExceptiontry-catchで処理するよりも、基本的には事前にnullチェックを行うべきです。

C#
string name = null;

if (name != null)
{
Console.WriteLine(name.Length);
}
else
{
Console.WriteLine("名前が設定されていません。");
}

また、C#ではnull条件演算子を使うこともできます。

C#
string name = null;
Console.WriteLine(name?.Length);

try-catchは便利ですが、null参照のように事前に防げるエラーは、できるだけ条件分岐で回避しましょう。

3-4. 数値変換エラーをtry-catchで処理する例

文字列を数値に変換する場合、int.Parseを使うと、変換できない文字列でFormatExceptionが発生します。

C#
try
{
string input = "abc";
int number = int.Parse(input);

Console.WriteLine($"入力された数値: {number}");
}
catch (FormatException)
{
Console.WriteLine("数値に変換できない文字が入力されました。");
}

ただし、ユーザー入力の変換では、try-catchよりもint.TryParseを使うのがおすすめです。

C#
string input = "abc";

if (int.TryParse(input, out int number))
{
Console.WriteLine($"入力された数値: {number}");
}
else
{
Console.WriteLine("正しい数値を入力してください。");
}

TryParseは、変換に失敗しても例外を発生させず、falseを返します。入力チェックにはこちらの方が向いています。

3-5. コンソールアプリで使える実用的な例外処理サンプル

次は、コンソールアプリでユーザーから数値を入力してもらい、割り算を行うサンプルです。

C#
try
{
Console.Write("割られる数を入力してください: ");
int x = int.Parse(Console.ReadLine());

Console.Write("割る数を入力してください: ");
int y = int.Parse(Console.ReadLine());

int result = x / y;

Console.WriteLine($"計算結果: {result}");
}
catch (FormatException)
{
Console.WriteLine("数値を入力してください。");
}
catch (DivideByZeroException)
{
Console.WriteLine("0で割ることはできません。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}

この例では、数値以外を入力した場合はFormatException、割る数に0を入力した場合はDivideByZeroExceptionが発生します。

最後にcatch (Exception ex)を書いておくことで、想定外の例外にも対応できます。ただし、実務ではログ出力などもあわせて行うのが一般的です。

4. 複数のcatchを使って例外の種類ごとに処理を分ける方法

C#では、1つのtryに対して複数のcatchを書くことができます。

例外の種類によって表示するメッセージや処理内容を変えたい場合に便利です。

4-1. 複数catchの基本構文

複数のcatchは、次のように並べて書きます。

C#
try
{
// 例外が発生する可能性のある処理
}
catch (FormatException)
{
// 文字列の形式が正しくない場合の処理
}
catch (DivideByZeroException)
{
// 0で割った場合の処理
}
catch (Exception)
{
// その他の例外の処理
}

発生した例外の型に一致するcatchが上から順に探されます。最初に一致したcatchだけが実行されます。

4-2. DivideByZeroExceptionの使い方

DivideByZeroExceptionは、整数を0で割ったときに発生します。

C#
try
{
int a = 10;
int b = 0;

Console.WriteLine(a / b);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("0で割ることはできません。");
Console.WriteLine(ex.Message);
}

割り算では、割る数が0にならないように事前チェックを入れることが基本です。ただし、計算処理が複雑な場合や外部から値を受け取る場合には、try-catchで備えておくこともあります。

4-3. NullReferenceExceptionの使い方

NullReferenceExceptionは、nullのオブジェクトにアクセスしたときに発生します。

C#
try
{
string message = null;
Console.WriteLine(message.ToUpper());
}
catch (NullReferenceException)
{
Console.WriteLine("値がnullです。処理を続行できません。");
}

この例では、messageがnullであるにもかかわらず、ToUpper()を呼び出そうとしているため例外が発生します。

ただし、null参照はプログラムの設計やチェック不足によって起こることが多いため、基本的にはnullチェックやnull許容参照型を活用して防ぐべきです。

4-4. FormatExceptionの使い方

FormatExceptionは、文字列の形式が正しくないときに発生します。

C#
try
{
string input = "2024年";
int year = int.Parse(input);

Console.WriteLine(year);
}
catch (FormatException)
{
Console.WriteLine("入力形式が正しくありません。数字だけを入力してください。");
}

int.ParseDateTime.Parsedecimal.Parseなどでは、形式が合わない文字列を変換しようとするとFormatExceptionが発生します。

ユーザー入力では、できるだけTryParse系メソッドを使う方が安全です。

4-5. catchの順番で注意すべきポイント

複数のcatchを書くときは、具体的な例外型を先に書き、広い例外型を後に書く必要があります。

たとえば、次の順番は正しいです。

C#
try
{
int number = int.Parse("abc");
}
catch (FormatException)
{
Console.WriteLine("形式が正しくありません。");
}
catch (Exception)
{
Console.WriteLine("その他の例外です。");
}

一方、次のようにExceptionを先に書くと、後ろのFormatExceptionには到達できません。

C#
try
{
int number = int.Parse("abc");
}
catch (Exception)
{
Console.WriteLine("例外が発生しました。");
}
catch (FormatException)
{
Console.WriteLine("形式が正しくありません。");
}

FormatExceptionExceptionの一種なので、先にExceptionで捕まってしまうからです。

4-6. Exceptionを最後に書くべき理由

Exceptionは多くの例外の基底クラスです。そのため、catch (Exception)は幅広い例外を捕まえます。

便利ではありますが、先に書いてしまうと、個別の例外処理が実行されなくなります。

そのため、複数のcatchを書く場合は、次の順番を意識しましょう。

C#
try
{
// 処理
}
catch (FileNotFoundException)
{
// ファイルがない場合
}
catch (IOException)
{
// 入出力エラー
}
catch (Exception)
{
// その他の例外
}

具体的な例外を先に、一般的な例外を最後に書くのが基本です。

5. finallyの使い方|例外の有無に関係なく実行したい処理

finallyは、例外が発生してもしなくても必ず実行したい処理を書くためのブロックです。

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

5-1. finallyとは何か

finallyは、try-catchの後に書くことができるブロックです。

C#
try
{
Console.WriteLine("tryブロック");
}
catch
{
Console.WriteLine("catchブロック");
}
finally
{
Console.WriteLine("finallyブロック");
}

例外が発生しなかった場合でも、発生した場合でも、finallyブロックは実行されます。

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

try-catch-finallyの基本構文は次のとおりです。

C#
try
{
// 例外が発生する可能性のある処理
}
catch (Exception ex)
{
// 例外が発生したときの処理
Console.WriteLine(ex.Message);
}
finally
{
// 例外の有無に関係なく実行したい処理
}

たとえば、次のように使います。

C#
try
{
int result = 10 / 0;
Console.WriteLine(result);
}
catch (DivideByZeroException)
{
Console.WriteLine("0で割ることはできません。");
}
finally
{
Console.WriteLine("計算処理を終了します。");
}

この場合、例外が発生してcatchが実行された後、最後にfinallyが実行されます。

5-3. ファイルやDB接続の後処理でfinallyを使う例

finallyは、リソースの解放処理でよく使われます。

C#
StreamReader reader = null;

try
{
reader = new StreamReader("data.txt");
string text = reader.ReadToEnd();

Console.WriteLine(text);
}
catch (IOException ex)
{
Console.WriteLine("ファイル処理中にエラーが発生しました。");
Console.WriteLine(ex.Message);
}
finally
{
if (reader != null)
{
reader.Close();
Console.WriteLine("ファイルを閉じました。");
}
}

このコードでは、ファイル読み込みの途中で例外が発生しても、finallyでファイルを閉じることができます。

ただし、現在のC#では、ファイルやDB接続のようなリソース管理にはusing文を使う方が一般的です。

5-4. returnがある場合でもfinallyは実行されるのか

trycatchの中にreturnがある場合でも、通常はfinallyが実行されます。

C#
static int GetNumber()
{
try
{
return 10;
}
finally
{
Console.WriteLine("finallyが実行されました。");
}
}

この場合、return 10;でメソッドを抜ける前に、finallyブロックが実行されます。

つまり、finallyは「最後に必ず実行したい後処理」を書く場所として使えます。

ただし、プロセスが強制終了した場合や、アプリケーション自体が異常終了した場合など、特殊なケースでは実行されないこともあります。

5-5. using文とfinallyの使い分け

ファイル、DB接続、ネットワーク接続など、使い終わったら解放が必要なオブジェクトにはusing文を使うのが便利です。

C#
try
{
using (StreamReader reader = new StreamReader("data.txt"))
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
}
catch (IOException ex)
{
Console.WriteLine("ファイル読み込みに失敗しました。");
Console.WriteLine(ex.Message);
}

using文を使うと、ブロックを抜けるときに自動的にDisposeが呼ばれます。これは内部的にはfinallyによる後処理に近い考え方です。

リソース解放にはusing、それ以外の必ず実行したい処理にはfinallyを使う、と覚えるとよいでしょう。

6. throwの使い方|例外を発生させる・再スローする方法

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

自分でエラー状態を検出して例外を投げたり、catchした例外をもう一度上位の処理に渡したりするときに使います。

6-1. throwとは何か

throwは、例外を発生させる命令です。

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

このコードを実行すると、Exceptionが発生します。

たとえば、メソッドに不正な引数が渡された場合に、ArgumentExceptionを投げることがあります。

C#
static void SetAge(int age)
{
if (age < 0)
{
throw new ArgumentException("年齢に負の値は指定できません。");
}

Console.WriteLine($"年齢: {age}");
}

6-2. 自分で例外を発生させるサンプル

次の例では、ユーザー名が空の場合に例外を発生させています。

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

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

呼び出し側では、try-catchで例外を受け取ることができます。

C#
try
{
RegisterUser("");
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}

このように、不正な状態を見つけた時点でthrowを使うと、呼び出し元にエラーを明確に伝えられます。

6-3. catchした例外を再スローする方法

catchした例外を、さらに呼び出し元へ渡したい場合は再スローします。

C#
try
{
string text = File.ReadAllText("data.txt");
}
catch (IOException ex)
{
Console.WriteLine("ログにエラーを記録します。");
Console.WriteLine(ex.Message);

throw;
}

この例では、catch内でログを出力したあと、throw;で同じ例外を再度投げています。

再スローは、現在のメソッドでは完全に処理できない例外を、上位の処理に任せたいときに使います。

6-4. throwとthrow exの違い

C#では、再スローするときにthrow;throw ex;の違いに注意が必要です。

基本的には、再スローにはthrow;を使います。

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

throw;は、元の例外情報やスタックトレースを保ったまま再スローします。

一方、次のようなthrow ex;は避けるべきです。

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

throw ex;を使うと、例外の発生位置に関する情報が再スローした場所に置き換わり、原因調査が難しくなることがあります。

そのため、catchした例外をそのまま再スローする場合は、throw;を使いましょう。

6-5. カスタム例外を作成するべきケース

C#では、自分で例外クラスを作ることもできます。これをカスタム例外と呼びます。

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

使うときは次のように書きます。

C#
throw new UserNotFoundException("指定されたユーザーが見つかりません。");

カスタム例外は、業務システムなどで「在庫不足」「ユーザー未登録」「権限不足」など、アプリ独自のエラーを明確に表したい場合に役立ちます。

ただし、標準の例外型で十分表現できる場合は、無理にカスタム例外を作る必要はありません。

7. C#のtry-catchでよくあるエラーと対策

C#のtry-catchを使っていても、思ったように例外を捕まえられなかったり、同じ例外が何度も発生したりすることがあります。

ここでは、初心者がつまずきやすい代表的な例外と対策を解説します。

7-1. catchされない例外が発生する原因

try-catchを書いているのに例外がcatchされない場合、よくある原因は、例外がtryブロックの外で発生していることです。

C#
int result = 10 / 0;

try
{
Console.WriteLine(result);
}
catch
{
Console.WriteLine("エラーが発生しました。");
}

このコードでは、例外がtryの前で発生しているため、catchでは捕まえられません。

正しくは、例外が発生する可能性のある処理をtryの中に入れます。

C#
try
{
int result = 10 / 0;
Console.WriteLine(result);
}
catch
{
Console.WriteLine("エラーが発生しました。");
}

また、非同期処理ではawaitしていないTask内の例外が、外側のtry-catchで捕まらないことがあります。

7-2. NullReferenceExceptionの原因と対策

NullReferenceExceptionは、C#初心者がよく遭遇する例外です。

原因は、nullの変数に対してメンバーへアクセスしていることです。

C#
string name = null;
Console.WriteLine(name.Length);

対策としては、事前にnullチェックを行います。

C#
if (name != null)
{
Console.WriteLine(name.Length);
}
else
{
Console.WriteLine("nameはnullです。");
}

また、null条件演算子を使う方法もあります。

C#
Console.WriteLine(name?.Length);

NullReferenceExceptiontry-catchで処理するよりも、設計や事前チェックで防ぐことが重要です。

7-3. IndexOutOfRangeExceptionの原因と対策

IndexOutOfRangeExceptionは、配列の範囲外にアクセスしたときに発生します。

C#
int[] numbers = { 10, 20, 30 };

Console.WriteLine(numbers[3]);

この配列のインデックスは0から2までです。numbers[3]は存在しないため、例外が発生します。

対策としては、配列の長さを確認してからアクセスします。

C#
int index = 3;

if (index >= 0 && index < numbers.Length)
{
Console.WriteLine(numbers[index]);
}
else
{
Console.WriteLine("指定されたインデックスは範囲外です。");
}

配列やリストを扱うときは、LengthCountを使って範囲を確認しましょう。

7-4. FormatExceptionの原因と対策

FormatExceptionは、文字列の形式が期待と合わない場合に発生します。

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

この例では、"abc"は整数に変換できないため、FormatExceptionが発生します。

ユーザー入力を扱う場合は、ParseではなくTryParseを使うのが安全です。

C#
string input = "abc";

if (int.TryParse(input, out int number))
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("数値を入力してください。");
}

日付変換でも同じように、DateTime.TryParseを使えます。

C#
if (DateTime.TryParse("2024/01/01", out DateTime date))
{
Console.WriteLine(date);
}
else
{
Console.WriteLine("日付形式が正しくありません。");
}

7-5. IOExceptionの原因と対策

IOExceptionは、ファイルやストリームなどの入出力処理で発生する例外です。

たとえば、ファイルが使用中である、読み書きに失敗した、パスに問題がある、ディスクに空き容量がないといったケースがあります。

C#
try
{
string text = File.ReadAllText("data.txt");
Console.WriteLine(text);
}
catch (IOException ex)
{
Console.WriteLine("ファイル操作中にエラーが発生しました。");
Console.WriteLine(ex.Message);
}

ファイル操作では、File.Existsで存在確認をすることも有効です。

C#
string path = "data.txt";

if (File.Exists(path))
{
string text = File.ReadAllText(path);
Console.WriteLine(text);
}
else
{
Console.WriteLine("ファイルが存在しません。");
}

ただし、存在確認後にファイルが削除される可能性もあるため、最終的にはtry-catchで備えることが大切です。

7-6. try-catchを書いているのにプログラムが止まる理由

try-catchを書いているのにプログラムが止まる場合、主な原因はいくつかあります。

まず、例外がtryブロックの外で発生しているケースです。例外が発生する処理が正しくtryの中に入っているか確認しましょう。

次に、捕まえたい例外型とcatchに書いた型が一致していないケースです。

C#
try
{
int.Parse("abc");
}
catch (DivideByZeroException)
{
Console.WriteLine("0除算エラーです。");
}

この場合、実際に発生するのはFormatExceptionなので、DivideByZeroExceptionではcatchできません。

また、Visual Studioのデバッグ実行中は、例外が発生した時点で一時停止する設定になっている場合があります。この場合、プログラムが完全に止まったわけではなく、デバッガが例外発生箇所で停止しているだけのことがあります。

7-7. Visual Studioで例外発生箇所を確認する方法

Visual Studioでは、例外が発生した行でデバッグ実行が停止します。停止した行を確認することで、どこでエラーが起きたのかを把握できます。

また、例外が発生したときには、例外の型やメッセージが表示されます。ex.Messageだけでなく、ex.StackTraceを確認すると、どのメソッドから呼び出されて例外が発生したのかがわかります。

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

開発中は、エラーメッセージとスタックトレースを確認する習慣をつけると、原因調査がしやすくなります。

8. try-catchを使うときの注意点とNG例

try-catchは便利ですが、使い方を間違えると、かえってバグの原因を見つけにくくなります。

ここでは、初心者がやりがちなNG例と注意点を紹介します。

8-1. 何でもExceptionで握りつぶしてはいけない理由

次のように、すべての例外をExceptionで捕まえて何もしないコードは危険です。

C#
try
{
// 何らかの処理
}
catch (Exception)
{
}

このコードでは、エラーが起きても何も表示されず、ログも残りません。プログラムは一見動いているように見えても、内部では重要な処理に失敗している可能性があります。

Exceptionでまとめてcatchする場合でも、最低限ログを残すべきです。

C#
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}

実務では、ログファイルやログ出力ライブラリに記録するのが一般的です。

8-2. 空のcatchブロックが危険な理由

空のcatchブロックは、例外を完全に無視してしまいます。

C#
try
{
File.ReadAllText("data.txt");
}
catch
{
}

このようなコードでは、ファイル読み込みに失敗しても何もわかりません。後続処理で別のエラーが発生し、原因が追いにくくなることがあります。

少なくとも、エラー内容をログに残しましょう。

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

例外を無視してよいケースは非常に限られます。基本的には、何らかの対応を必ず書くようにしましょう。

8-3. tryブロックを広く囲みすぎる問題

次のように、広い範囲をまとめてtryで囲むと、どの処理で例外が発生したのかわかりにくくなります。

C#
try
{
ReadFile();
ConvertData();
SaveToDatabase();
SendMail();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

このコードでは、ファイル読み込み、データ変換、DB保存、メール送信のどこで失敗したのかがわかりにくいです。

必要に応じて、処理単位ごとに分けると原因を特定しやすくなります。

C#
try
{
ReadFile();
}
catch (IOException ex)
{
Console.WriteLine($"ファイル読み込みエラー: {ex.Message}");
}

try
{
SaveToDatabase();
}
catch (Exception ex)
{
Console.WriteLine($"DB保存エラー: {ex.Message}");
}

tryブロックは、例外が発生する可能性のある範囲に絞るのが基本です。

8-4. 例外処理と条件分岐を混同しない

try-catchは、通常の条件分岐の代わりに使うものではありません。

たとえば、次のように数値変換の失敗を毎回例外で処理するよりも、TryParseを使う方が適切です。

C#
try
{
int number = int.Parse(input);
}
catch (FormatException)
{
Console.WriteLine("数値ではありません。");
}

ユーザー入力のように失敗がよく起こる処理では、次のように書く方が自然です。

C#
if (int.TryParse(input, out int number))
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("数値を入力してください。");
}

例外は、通常の処理の流れではなく、異常な状態を扱うためのものです。

8-5. エラーメッセージをログに残す重要性

例外が発生したときは、原因を調査できるようにログを残すことが大切です。

最低限、次の情報を残すと役立ちます。

例外の種類、エラーメッセージ、スタックトレース、発生日時、処理中だったデータなどです。

簡単な例は次のとおりです。

C#
catch (Exception ex)
{
Console.WriteLine($"発生日時: {DateTime.Now}");
Console.WriteLine($"例外型: {ex.GetType().Name}");
Console.WriteLine($"メッセージ: {ex.Message}");
Console.WriteLine($"スタックトレース: {ex.StackTrace}");
}

開発中はコンソール出力でもよいですが、実際のアプリではログファイルやログ管理サービスに記録することが多いです。

8-6. ユーザーに見せるエラー文と開発者向けログの分け方

ユーザーに表示するメッセージと、開発者が確認するログは分けて考えるべきです。

たとえば、次のような内部情報をそのまま表示するのは避けましょう。

C#
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}

ex.ToString()には、スタックトレースや内部パスなどが含まれる場合があります。

ユーザーには、わかりやすいメッセージを表示します。

C#
catch (Exception ex)
{
Console.WriteLine("処理中に問題が発生しました。時間をおいて再度お試しください。");

// 開発者向けログ
Console.Error.WriteLine(ex.ToString());
}

業務システムやWebアプリでは、ユーザー向けには簡潔な説明を出し、詳細はログに残すのが基本です。

9. async/awaitでtry-catchを使う方法

C#では、非同期処理でもtry-catchを使えます。

API通信、ファイル読み込み、データベースアクセスなど、時間のかかる処理ではasync/awaitを使うことが多く、例外処理の書き方も重要になります。

9-1. 非同期処理でもtry-catchは使えるのか

asyncメソッド内でも、通常の処理と同じようにtry-catchを書けます。

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

awaitしている非同期処理の中で例外が発生した場合、catchブロックで受け取ることができます。

9-2. asyncメソッド内で例外をcatchする基本例

次の例では、存在しないファイルを非同期で読み込もうとしています。

C#
static async Task ReadFileAsync()
{
try
{
string text = await File.ReadAllTextAsync("data.txt");
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (IOException ex)
{
Console.WriteLine("ファイル読み込み中にエラーが発生しました。");
Console.WriteLine(ex.Message);
}
}

await File.ReadAllTextAsyncで例外が発生した場合、対応するcatchが実行されます。

非同期処理でも、基本的な考え方は通常のtry-catchと同じです。

9-3. Taskの例外処理で注意すべきポイント

asyncメソッドで発生した例外は、Taskに保持されます。そして、そのTaskawaitしたタイミングで例外として再び発生します。

C#
try
{
await DoWorkAsync();
}
catch (Exception ex)
{
Console.WriteLine("非同期処理で例外が発生しました。");
Console.WriteLine(ex.Message);
}

一方、Task.Wait()Task.Resultを使うと、例外がAggregateExceptionに包まれる場合があります。

C#
try
{
DoWorkAsync().Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
}

現在のC#では、基本的にWait()Resultよりもawaitを使うことが推奨されます。

9-4. awaitしないと例外をcatchできないケース

次のコードでは、外側のtry-catchで例外を捕まえられない場合があります。

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

DoWorkAsync()を呼び出していますが、awaitしていません。そのため、非同期処理の中で後から発生した例外は、このcatchでは捕まえられません。

正しくは次のように書きます。

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

非同期メソッドの例外を処理したい場合は、基本的にawaittry-catchをセットで使いましょう。

9-5. API通信やファイル処理での実践例

API通信では、ネットワーク障害やタイムアウトなどで例外が発生する可能性があります。

C#
static async Task GetApiDataAsync()
{
using HttpClient client = new HttpClient();

try
{
string result = await client.GetStringAsync("https://example.com/api/data");
Console.WriteLine(result);
}
catch (HttpRequestException ex)
{
Console.WriteLine("API通信に失敗しました。");
Console.WriteLine(ex.Message);
}
catch (TaskCanceledException)
{
Console.WriteLine("通信がタイムアウトしました。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}
}

API通信やファイル処理では、失敗することを前提に例外処理を書くことが大切です。

10. try-catchと他の例外処理方法の使い分け

C#では、すべてのエラー対策をtry-catchだけで行うわけではありません。

if文、TryParseusing文、ログ出力などを適切に組み合わせることで、読みやすく安全なコードになります。

10-1. if文で防ぐべきエラーとtry-catchで処理すべき例外

事前に簡単に確認できるものは、if文で防ぐのが基本です。

たとえば、0除算は事前にチェックできます。

C#
if (y != 0)
{
Console.WriteLine(x / y);
}
else
{
Console.WriteLine("0では割れません。");
}

nullチェックもif文で対応できます。

C#
if (user != null)
{
Console.WriteLine(user.Name);
}

一方、ファイルアクセスやAPI通信のように、確認後に状況が変わる可能性があるものはtry-catchで備える必要があります。

C#
try
{
string text = File.ReadAllText(path);
}
catch (IOException ex)
{
Console.WriteLine(ex.Message);
}

事前チェックできるものはif、実行してみないとわからないものはtry-catchと考えるとよいでしょう。

10-2. TryParseを使うべき場面

ユーザー入力を数値や日付に変換する場合は、try-catchよりもTryParseを使うのがおすすめです。

C#
Console.Write("年齢を入力してください: ");
string input = Console.ReadLine();

if (int.TryParse(input, out int age))
{
Console.WriteLine($"年齢は{age}歳です。");
}
else
{
Console.WriteLine("正しい数値を入力してください。");
}

TryParseは、変換に失敗しても例外を発生させません。そのため、入力ミスがよく起こる場面に適しています。

int.Parseは、入力が正しいことが保証されている場合に使いましょう。

10-3. using文でリソース解放する場面

ファイル、DB接続、ネットワーク通信など、使い終わったら解放が必要なオブジェクトにはusing文を使います。

C#
try
{
using StreamReader reader = new StreamReader("data.txt");

string text = reader.ReadToEnd();
Console.WriteLine(text);
}
catch (IOException ex)
{
Console.WriteLine("ファイル処理に失敗しました。");
Console.WriteLine(ex.Message);
}

usingを使うと、処理が正常終了しても例外で中断しても、リソースが適切に解放されます。

finallyで手動解放するよりもコードが短く、ミスが起きにくくなります。

10-4. ログ出力ライブラリと組み合わせる方法

実務では、Console.WriteLineだけでなく、ログ出力ライブラリを使って例外情報を記録することが多いです。

イメージとしては次のようになります。

C#
try
{
SaveOrder();
}
catch (Exception ex)
{
logger.LogError(ex, "注文保存中にエラーが発生しました。");
Console.WriteLine("注文の保存に失敗しました。");
}

ログには開発者向けの詳細情報を残し、ユーザーにはわかりやすいメッセージを表示します。

ログを残しておくと、後から障害調査や再発防止を行いやすくなります。

10-5. アプリ開発・業務システム・Unityでの使い分け

アプリ開発では、ユーザー操作や外部通信に関連する部分でtry-catchを使うことが多いです。たとえば、ログイン処理、ファイル保存、API通信などです。

業務システムでは、データベース操作、ファイル連携、帳票出力、外部システム連携などで例外処理が重要になります。ログ出力やエラー通知もあわせて設計する必要があります。

Unityでは、ゲーム中の処理で例外が頻発するとパフォーマンスや動作に影響する可能性があります。そのため、通常のゲームロジックでは事前チェックを重視し、ファイル読み込みや通信処理など失敗が起こり得る部分にtry-catchを使うのが基本です。

11. C#のtry-catchのベストプラクティス

C#でtry-catchを使うときは、ただ例外を捕まえればよいわけではありません。

保守しやすく、原因調査しやすく、安全なコードにするためには、いくつかのポイントを意識する必要があります。

11-1. 想定できる例外だけをcatchする

できるだけ、具体的な例外型をcatchしましょう。

C#
try
{
string text = File.ReadAllText("data.txt");
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルへのアクセス権限がありません。");
}
catch (IOException)
{
Console.WriteLine("ファイル読み込み中にエラーが発生しました。");
}

すべてをExceptionでまとめると、想定外のバグまで吸収してしまう可能性があります。

ただし、アプリ全体の最後の防御としてExceptionをcatchする場面もあります。その場合は、必ずログを残しましょう。

11-2. 例外の内容をログに残す

例外が発生したら、原因調査のためにログを残すことが大切です。

C#
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
}

ex.Messageだけでは情報が足りない場合があります。ex.ToString()には、例外の型、メッセージ、スタックトレースなどが含まれるため、開発者向けログとして役立ちます。

ただし、ユーザー画面にそのまま表示するのは避けましょう。

11-3. ユーザーにわかりやすいメッセージを表示する

ユーザーには、技術的な詳細ではなく、何が起きたのか、どうすればよいのかがわかるメッセージを表示します。

C#
catch (FileNotFoundException)
{
Console.WriteLine("必要なファイルが見つかりません。設定を確認してください。");
}
catch (HttpRequestException)
{
Console.WriteLine("通信に失敗しました。ネットワーク接続を確認してください。");
}

「NullReferenceExceptionが発生しました」のようなメッセージは、一般ユーザーには意味がわかりにくいです。

ユーザー向けには簡潔で具体的な説明、開発者向けには詳細なログというように分けて扱いましょう。

11-4. 必要な後処理はfinallyまたはusingで行う

ファイルやDB接続など、後処理が必要なものはfinallyまたはusingで確実に解放します。

C#
using StreamReader reader = new StreamReader("data.txt");
string text = reader.ReadToEnd();

usingを使える場合は、基本的にusingを優先するとコードが簡潔になります。

finallyは、リソース解放以外にも、ステータス更新や一時ファイル削除など、必ず実行したい処理がある場合に使います。

11-5. 例外を握りつぶさず適切に再スローする

現在の場所で例外を完全に処理できない場合は、ログを残したうえで再スローすることもあります。

C#
try
{
SaveData();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
throw;
}

このようにthrow;を使うと、元の例外情報を保ったまま上位に渡せます。

例外をcatchしただけで何もせず処理を続けると、データ不整合や別のエラーにつながることがあります。

11-6. 保守しやすい例外処理コードを書くコツ

保守しやすい例外処理を書くには、まずtryブロックを必要な範囲に絞ることが大切です。

また、例外ごとに適切なメッセージを用意し、ログには詳細情報を残しましょう。

同じような例外処理が複数箇所にある場合は、共通メソッドや共通クラスにまとめることも検討できます。

さらに、通常の入力チェックやnullチェックまで何でもtry-catchに任せるのではなく、if文やTryParseを適切に使うことで、読みやすいコードになります。

12. C#のtry-catchに関するよくある質問

最後に、C#のtry-catchについて初心者が疑問に思いやすいポイントをQ&A形式で解説します。

12-1. try-catchはどこに書けばいい?

try-catchは、例外が発生する可能性があり、その場で対処したい処理の周辺に書きます。

たとえば、ファイル読み込み、API通信、DB接続、ユーザー入力の変換などです。

C#
try
{
string text = File.ReadAllText("data.txt");
}
catch (IOException ex)
{
Console.WriteLine(ex.Message);
}

すべての処理を大きく囲むのではなく、失敗する可能性がある処理単位で書くのが基本です。

12-2. catchにExceptionを書くだけでもいい?

catch (Exception ex)だけでも多くの例外を捕まえることはできます。

C#
try
{
// 処理
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

ただし、例外の種類ごとに適切な対応をしたい場合は、具体的な例外型をcatchするべきです。

C#
catch (FormatException)
{
Console.WriteLine("入力形式が正しくありません。");
}
catch (IOException)
{
Console.WriteLine("ファイル処理に失敗しました。");
}
catch (Exception)
{
Console.WriteLine("予期しないエラーが発生しました。");
}

Exceptionは最後の保険として使うのがよいでしょう。

12-3. finallyは必ず必要?

finallyは必ず必要ではありません。

例外が発生したときの処理だけを書きたい場合は、try-catchだけで十分です。

C#
try
{
int result = 10 / 0;
}
catch (DivideByZeroException)
{
Console.WriteLine("0で割ることはできません。");
}

一方、例外の有無に関係なく必ず実行したい後処理がある場合は、finallyを使います。

C#
finally
{
Console.WriteLine("処理を終了します。");
}

リソース解放が目的であれば、using文を使えるかも検討しましょう。

12-4. try-catchを多用すると処理は遅くなる?

try-catchを書くだけで大きく処理が遅くなるわけではありません。

ただし、例外を発生させる処理は通常の条件分岐よりもコストが高いです。そのため、通常の処理の流れとして例外を使うのは避けるべきです。

たとえば、数値変換の失敗がよく起こるユーザー入力では、int.Parsetry-catchよりもint.TryParseを使う方が適しています。

C#
if (int.TryParse(input, out int number))
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("数値を入力してください。");
}

例外は、あくまで異常時のための仕組みとして使いましょう。

12-5. 例外メッセージはそのまま表示していい?

開発中であれば、ex.Messageを表示して確認することはよくあります。

しかし、本番環境でユーザーにそのまま表示するのは注意が必要です。

例外メッセージには、内部のファイルパス、システム構成、SQL情報などが含まれる場合があります。そのため、ユーザーにはわかりやすい一般的なメッセージを表示し、詳細はログに残すのが安全です。

C#
catch (Exception ex)
{
Console.WriteLine("処理中にエラーが発生しました。");
Console.Error.WriteLine(ex.ToString());
}

ユーザー向けメッセージと開発者向けログを分けることが大切です。

12-6. 初心者はまずどの例外を覚えるべき?

初心者がまず覚えておきたいC#の例外は、次のようなものです。

NullReferenceExceptionは、nullの変数にアクセスしたときに発生します。

FormatExceptionは、文字列を数値や日付に変換できないときに発生します。

DivideByZeroExceptionは、整数を0で割ったときに発生します。

IndexOutOfRangeExceptionは、配列の範囲外にアクセスしたときに発生します。

FileNotFoundExceptionは、指定したファイルが見つからないときに発生します。

IOExceptionは、ファイルやストリームなどの入出力処理で発生します。

最初からすべてを覚える必要はありません。実際にエラーに遭遇したときに、例外名、原因、対策を少しずつ覚えていくのがおすすめです。

まとめ

C#のtry-catchは、プログラム実行中に発生する例外を安全に処理するための重要な構文です。

tryブロックには例外が発生する可能性のある処理を書き、catchブロックには例外が発生したときの対応を書きます。複数のcatchを使えば、DivideByZeroExceptionFormatExceptionIOExceptionなど、例外の種類ごとに処理を分けられます。

また、finallyを使うと、例外の有無に関係なく後処理を実行できます。ファイルやDB接続などのリソース解放には、using文を使う方法も覚えておくと便利です。

throwを使えば、自分で例外を発生させたり、catchした例外を再スローしたりできます。再スローするときは、スタックトレースを保つためにthrow;を使うのが基本です。

一方で、try-catchを何でも使えばよいわけではありません。nullチェック、範囲チェック、数値変換のように事前に防げるエラーは、if文やTryParseを使う方が適切です。

C#の例外処理では、想定できる例外だけをcatchし、必要な情報をログに残し、ユーザーにはわかりやすいメッセージを表示することが大切です。

try-catchを正しく使えるようになると、エラーに強く、保守しやすいC#プログラムを書けるようになります。