C#のtry-catch入門|例外処理の基本・書き方・使いどころを初心者向けに解説

はじめに

C#でプログラムを書いていると、コードの文法は正しいのに、実行中にエラーが発生することがあります。たとえば、存在しないファイルを読み込もうとしたり、文字列を数値に変換しようとして失敗したり、通信先のサーバーに接続できなかったりするケースです。

このような実行時のエラーに備えるために使うのが、C#のtry-catchです。

try-catchを使うと、エラーが発生したときにプログラムを突然終了させるのではなく、エラーの内容に応じてメッセージを表示したり、ログを残したり、別の処理に切り替えたりできます。

また、C#ではtry-catchとあわせてfinallyusingを使う場面も多くあります。特にファイル、データベース接続、ネットワーク通信などの外部リソースを扱う場合、try-catchusingの違いを理解しておくことはとても重要です。

この記事では、C#のtry-catchの基本、書き方、使いどころ、usingとの関係、よくある間違い、実務で意識したいベストプラクティスまで、初心者向けにわかりやすく解説します。

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

C#のtry-catchは、プログラム実行中に発生する例外に対応するための構文です。

例外とは、プログラムが通常どおり処理を続けられなくなった状態を表します。たとえば、次のような状況で例外が発生します。

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

このコードでは、"abc"という文字列を整数に変換しようとしています。しかし、abcは数値ではないため、C#は変換できずに例外を発生させます。

このような例外を適切に処理するために、try-catchを使います。

1-1. try-catchは実行時エラーに備えるための仕組み

try-catchは、エラーが起きる可能性のある処理をtryブロックに書き、エラーが発生したときの処理をcatchブロックに書く仕組みです。

基本的な考え方は、次のとおりです。

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

tryの中で例外が発生しなければ、catchの中は実行されません。
一方、tryの中で例外が発生すると、その時点で通常の処理は中断され、対応するcatchブロックに処理が移ります。

たとえば、数値変換に失敗したときにメッセージを表示するコードは次のようになります。

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

このコードでは、int.Parse("abc")で例外が発生しますが、catchで受け止めているため、プログラムが突然終了するのを防げます。

1-2. 例外処理が必要になる代表的な場面

C#で例外処理が必要になる場面は多くあります。特に、プログラムの外部に依存する処理では、try-catchがよく使われます。

代表的な例は次のとおりです。

C#
// ユーザー入力の変換
int age = int.Parse(input);

// ファイル読み込み
string text = File.ReadAllText("data.txt");

// API通信
HttpResponseMessage response = await client.GetAsync(url);

// データベース接続
connection.Open();

これらの処理は、開発者が完全に制御できない要素を含んでいます。

ユーザーが正しい値を入力するとは限りません。ファイルが存在しない可能性もあります。通信先のサーバーが停止していることもあります。データベースの接続情報が間違っていることもあります。

このような場面では、例外が発生することを前提にして、try-catchで安全に処理することが大切です。

1-3. コンパイルエラーと例外の違い

初心者が混同しやすいものに、コンパイルエラーと例外があります。

コンパイルエラーは、プログラムを実行する前に見つかるエラーです。たとえば、文法ミス、変数名の間違い、型の不一致などが該当します。

C#
int number = "abc";

このコードは、文字列をint型の変数に代入しようとしているため、コンパイルエラーになります。C#の文法や型のルールに違反しているので、そもそも実行できません。

一方、例外はプログラムの実行中に発生するエラーです。

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

このコードは文法的には正しいため、コンパイルは通ります。しかし、実行すると"abc"を整数に変換できないため、例外が発生します。

つまり、コンパイルエラーは「実行前にわかるエラー」、例外は「実行中に起きるエラー」と考えると理解しやすいです。

1-4. try-catchを使うとプログラムがどう安全になるのか

try-catchを使うと、例外が発生してもプログラム全体が突然停止するのを防げます。

たとえば、ファイルを読み込む処理でファイルが存在しない場合、try-catchがなければプログラムがエラーで終了する可能性があります。

C#
string text = File.ReadAllText("data.txt");
Console.WriteLine(text);

しかし、try-catchを使えば、ファイルが見つからない場合にユーザーへわかりやすいメッセージを表示できます。

C#
try
{
string text = File.ReadAllText("data.txt");
Console.WriteLine(text);
}
catch
{
Console.WriteLine("ファイルを読み込めませんでした。");
}

これにより、エラーが発生してもプログラムを安全に終了させたり、別の処理へ進めたりできます。

実務では、try-catchを使ってログを残すことも重要です。ログがあれば、あとから「どこで」「どのような例外が」「なぜ発生したのか」を調査しやすくなります。

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

C#のtry-catchは、基本構文を覚えればすぐに使えます。最初は難しく考えすぎず、「失敗する可能性のある処理をtryに書く」「失敗したときの処理をcatchに書く」と理解しましょう。

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

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

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

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

C#
catch
{
// 例外が発生したときの処理
}

たとえば、ユーザーが入力した文字列を数値に変換する処理は、失敗する可能性があります。そのため、tryブロックに書く候補になります。

C#
try
{
int number = int.Parse("100");
Console.WriteLine(number);
}
catch
{
Console.WriteLine("変換に失敗しました。");
}

tryの中で例外が発生しなければ、catchは実行されません。
例外が発生した場合だけ、catchが実行されます。

2-2. 基本的なtry-catchのサンプルコード

次のコードは、C#のtry-catchの基本例です。

C#
using System;

class Program
{
static void Main()
{
try
{
Console.WriteLine("数値を入力してください。");
string input = Console.ReadLine();

int number = int.Parse(input);

Console.WriteLine($"入力された数値は {number} です。");
}
catch
{
Console.WriteLine("数値として正しく入力してください。");
}

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

このコードでは、ユーザーが123のような数値を入力すれば正常に処理されます。
しかし、abcのような文字を入力すると、int.Parseで例外が発生し、catchブロックが実行されます。

try-catchを使っているため、入力ミスがあってもプログラムが突然終了せず、最後の「処理を終了します。」まで進むことができます。

2-3. Exceptionクラスを使った例外情報の取得方法

catchでは、発生した例外の情報を取得できます。そのときに使うのがExceptionクラスです。

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

Exception exと書くことで、発生した例外の情報をexという変数で受け取れます。

exには、例外のメッセージ、発生場所、例外の種類など、デバッグに役立つ情報が含まれています。

ただし、実務ではすべての例外をすぐにExceptionだけで受けるのではなく、できるだけ具体的な例外型でcatchするのが基本です。

C#
try
{
int number = int.Parse("abc");
}
catch (FormatException ex)
{
Console.WriteLine("入力形式が正しくありません。");
Console.WriteLine(ex.Message);
}

このように、数値変換の形式エラーであればFormatExceptionを使うと、何のエラーを想定しているのかがわかりやすくなります。

2-4. ex.Message・ex.StackTraceで確認できる内容

Exceptionクラスでよく使うプロパティに、MessageStackTraceがあります。

ex.Messageには、例外の概要メッセージが入っています。

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

たとえば、文字列を数値に変換できなかった場合は、入力文字列の形式が正しくないことを示すメッセージが表示されます。

一方、ex.StackTraceには、例外がどのメソッドのどのあたりで発生したかを追跡するための情報が入っています。

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

StackTraceは、開発中やログ出力で特に役立ちます。
ただし、一般ユーザーにそのまま表示するには情報が詳しすぎることがあります。実務では、ユーザーにはわかりやすいメッセージを表示し、開発者向けにはログに詳細情報を残すのがよい書き方です。

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

// 実務ではログファイルやログ基盤に出力する
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}

2-5. 初心者がまず覚えるべき最小構成

初心者がまず覚えるべきtry-catchの最小構成は、次の形です。

C#
try
{
// 失敗する可能性のある処理
}
catch (Exception ex)
{
// エラーが発生したときの処理
Console.WriteLine(ex.Message);
}

最初はこの形を覚えておけば十分です。

ただし、慣れてきたら次の点も意識しましょう。

tryブロックには、必要な処理だけを書くこと。
catchでは、エラーの内容を確認できるようにすること。
可能であれば、Exceptionではなく具体的な例外型を使うこと。
エラーを無視せず、ログやメッセージなど何らかの対応をすること。

try-catchは、ただ書けばよいものではありません。どの処理で例外が起こる可能性があるのか、例外が起きたらどう対応するのかを考えて使うことが大切です。

3. C#でtry-catchを使う具体例

ここからは、C#でtry-catchを使う具体例を見ていきます。初心者がよく出会う例外として、数値変換、ファイル読み込み、配列やリストの操作があります。

3-1. 数値変換でFormatExceptionをcatchする例

文字列を数値に変換する処理では、FormatExceptionが発生することがあります。

C#
using System;

class Program
{
static void Main()
{
Console.WriteLine("年齢を入力してください。");
string input = Console.ReadLine();

try
{
int age = int.Parse(input);
Console.WriteLine($"年齢は {age} 歳です。");
}
catch (FormatException)
{
Console.WriteLine("数字で入力してください。");
}
}
}

int.Parseは、文字列が正しい数値形式でない場合にFormatExceptionを発生させます。

たとえば、ユーザーが20と入力すれば成功します。
しかし、abc二十と入力すると、数値に変換できないためcatchが実行されます。

このように、ユーザー入力を扱う処理では、入力ミスを前提にした例外処理が必要になることがあります。

ただし、数値変換の場合はtry-catchではなく、int.TryParseを使う方法もよく使われます。

C#
string input = Console.ReadLine();

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

入力チェックのように失敗がよく起こる処理では、TryParseのような事前に判定できる方法を使うほうが読みやすい場合もあります。

3-2. ファイル読み込みでFileNotFoundExceptionをcatchする例

ファイルを読み込む処理では、指定したファイルが存在しない可能性があります。その場合、FileNotFoundExceptionが発生します。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
try
{
string text = File.ReadAllText("sample.txt");
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("指定されたファイルが見つかりません。");
}
}
}

このコードでは、sample.txtが存在しない場合にFileNotFoundExceptioncatchします。

ファイル操作では、他にも次のような例外が発生する可能性があります。

C#
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルにアクセスする権限がありません。");
}
catch (IOException)
{
Console.WriteLine("ファイルの読み込み中にエラーが発生しました。");
}

ファイルが存在しない、権限がない、別のプロセスが使用中、ディスクに問題があるなど、ファイル操作はさまざまな理由で失敗します。

そのため、ファイル読み込みや書き込みでは、try-catchを使って例外に備えることが重要です。

3-3. 配列・リスト操作で発生する例外の例

配列やリストでは、存在しない位置の要素にアクセスすると例外が発生します。

C#
using System;

class Program
{
static void Main()
{
int[] numbers = { 10, 20, 30 };

try
{
Console.WriteLine(numbers[5]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("配列の範囲外にアクセスしました。");
}
}
}

配列numbersには、要素が3つしかありません。
使えるインデックスは012です。

しかし、numbers[5]にアクセスしようとしているため、IndexOutOfRangeExceptionが発生します。

リストの場合は、範囲外のインデックスにアクセスするとArgumentOutOfRangeExceptionが発生することがあります。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
List<string> names = new List<string> { "田中", "佐藤", "鈴木" };

try
{
Console.WriteLine(names[10]);
}
catch (ArgumentOutOfRangeException)
{
Console.WriteLine("リストの範囲外にアクセスしました。");
}
}
}

ただし、配列やリストの範囲外アクセスは、事前にLengthCountを確認すれば防げることが多いです。

C#
int index = 5;

if (index >= 0 && index < numbers.Length)
{
Console.WriteLine(numbers[index]);
}
else
{
Console.WriteLine("指定された位置のデータは存在しません。");
}

このように、事前チェックで防げるものは、try-catchよりもif文で防ぐほうが自然です。

3-4. 例外ごとに処理を分ける書き方

C#では、catchを複数書くことで、例外の種類ごとに処理を分けられます。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
try
{
string text = File.ReadAllText("sample.txt");
int number = int.Parse(text);

Console.WriteLine($"読み込んだ数値は {number} です。");
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (FormatException)
{
Console.WriteLine("ファイルの内容が数値ではありません。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}
}
}

このコードでは、次のように例外ごとに対応を変えています。

ファイルが存在しない場合は、FileNotFoundExceptionを処理します。
ファイルの内容が数値でない場合は、FormatExceptionを処理します。
それ以外の予期しない例外は、最後のExceptionで処理します。

このように例外の種類ごとにcatchを分けると、エラーの原因に応じた適切な対応ができます。

3-5. catchの順番に注意すべき理由

複数のcatchを書く場合は、順番に注意が必要です。

C#では、上から順番にcatchが判定されます。
そのため、より具体的な例外を先に書き、より広い例外を後に書く必要があります。

正しい例は次のとおりです。

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

FormatExceptionExceptionを継承した例外です。
そのため、先にExceptionを書いてしまうと、FormatExceptionExceptionとして先に捕捉されてしまいます。

次のような順番はよくありません。

C#
try
{
int number = int.Parse("abc");
}
catch (Exception)
{
Console.WriteLine("例外が発生しました。");
}
// このcatchには到達できない
catch (FormatException)
{
Console.WriteLine("形式が正しくありません。");
}

このような書き方は、後ろのcatchが実行される可能性がないため問題になります。

catchを書くときは、「具体的な例外から先に、一般的な例外を後に」と覚えておきましょう。

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

C#の例外処理では、try-catchに加えてfinallyを使うことがあります。finallyは、例外が発生してもしなくても、最後に必ず実行したい処理を書くためのブロックです。

4-1. finallyブロックの基本構文

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

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

finallyは、tryの処理が成功した場合にも実行されます。
また、catchで例外を処理した場合にも実行されます。

たとえば、次のコードでは、例外が発生しても最後に「終了処理を実行します。」が表示されます。

C#
try
{
int number = int.Parse("abc");
}
catch (FormatException)
{
Console.WriteLine("数値に変換できませんでした。");
}
finally
{
Console.WriteLine("終了処理を実行します。");
}

4-2. finallyが実行されるタイミング

finallyは、tryブロックとcatchブロックの処理が終わったあとに実行されます。

例外が発生しない場合は、次の順番で処理されます。

C#
try
{
Console.WriteLine("tryを実行します。");
}
catch
{
Console.WriteLine("catchを実行します。");
}
finally
{
Console.WriteLine("finallyを実行します。");
}

この場合、catchは実行されず、tryのあとにfinallyが実行されます。

例外が発生した場合は、tryの途中で処理が中断され、catchに移り、そのあとfinallyが実行されます。

C#
try
{
Console.WriteLine("try開始");
int number = int.Parse("abc");
Console.WriteLine("この行は実行されません。");
}
catch
{
Console.WriteLine("catchを実行します。");
}
finally
{
Console.WriteLine("finallyを実行します。");
}

このコードでは、int.Parse("abc")で例外が発生するため、その下の行は実行されません。
その後、catchが実行され、最後にfinallyが実行されます。

4-3. ファイル・DB接続・外部リソースの後片付けに使う例

finallyは、ファイル、データベース接続、ネットワーク接続など、使い終わったあとに後片付けが必要な処理で使われます。

たとえば、ファイルを開いたあとには閉じる必要があります。
データベース接続を開いたあとには閉じる必要があります。

C#
StreamReader reader = null;

try
{
reader = new StreamReader("sample.txt");
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
finally
{
if (reader != null)
{
reader.Close();
}
}

このコードでは、例外が発生してもしなくても、最後にreader.Close()が実行されます。

ただし、現在のC#では、このようなリソース解放にはusingを使うことが多いです。usingを使うと、Disposeが自動的に呼ばれるため、コードをより簡潔に書けます。

4-4. try-catch-finallyのサンプルコード

try-catch-finallyを使ったサンプルコードを見てみましょう。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
StreamReader reader = null;

try
{
reader = new StreamReader("sample.txt");

string text = reader.ReadToEnd();
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりませんでした。");
}
catch (IOException ex)
{
Console.WriteLine("ファイルの読み込み中にエラーが発生しました。");
Console.WriteLine(ex.Message);
}
finally
{
if (reader != null)
{
reader.Dispose();
Console.WriteLine("ファイルを閉じました。");
}
}
}
}

このコードでは、ファイルの読み込みに成功しても失敗しても、最後にfinallyでファイルを閉じています。

Close()Dispose()は似ていますが、C#では外部リソースの解放にDispose()がよく使われます。
usingステートメントも内部的にはDispose()を呼び出す仕組みです。

4-5. finallyを使わなくてもよいケース

finallyは便利ですが、必ず使わなければならないわけではありません。

たとえば、単純な数値変換で後片付けが不要な場合、finallyは必要ありません。

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

このように、特に解放するリソースがない場合は、try-catchだけで十分です。

また、ファイルやデータベース接続などのリソース解放では、finallyよりもusingを使ったほうが簡潔に書ける場合が多いです。

C#
try
{
using StreamReader reader = new StreamReader("sample.txt");
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
catch (IOException ex)
{
Console.WriteLine("ファイル処理でエラーが発生しました。");
Console.WriteLine(ex.Message);
}

このように、usingを使えば、スコープを抜けるときに自動でDispose()が呼ばれます。

5. C#のusingとtry-catchの関係

C#では、try-catchとあわせてusingがよく登場します。どちらもエラー処理や安全なコードに関係しますが、役割は異なります。

try-catchは、例外を捕捉して処理するための構文です。
usingは、使い終わったリソースを自動的に解放するための構文です。

この違いを理解しておくと、ファイル操作やデータベース処理のコードが書きやすくなります。

5-1. usingには2種類ある|usingディレクティブとusingステートメント

C#のusingには、大きく分けて2種類あります。

1つ目は、名前空間を読み込むためのusingディレクティブです。

C#
using System;
using System.IO;
using System.Collections.Generic;

これは、クラス名を短く書くために使います。
たとえば、System.Console.WriteLineと書かずに、Console.WriteLineと書けるようになります。

2つ目は、リソース解放のためのusingステートメントです。

C#
using (StreamReader reader = new StreamReader("sample.txt"))
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}

こちらのusingは、ブロックを抜けるときにreader.Dispose()を自動的に呼び出します。

同じusingというキーワードですが、役割が違うため注意しましょう。

5-2. usingステートメントはリソース解放のための構文

usingステートメントは、IDisposableを実装しているオブジェクトのDispose()を自動的に呼び出すための構文です。

たとえば、StreamReaderはファイルを扱うクラスで、使い終わったらリソースを解放する必要があります。

C#
using (StreamReader reader = new StreamReader("sample.txt"))
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}

このコードでは、usingブロックを抜けたタイミングで、自動的にreader.Dispose()が呼ばれます。

これは、次のようなtry-finallyに近い動きをします。

C#
StreamReader reader = new StreamReader("sample.txt");

try
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
finally
{
if (reader != null)
{
reader.Dispose();
}
}

つまり、usingは例外を処理するためのものではなく、例外が発生してもしなくても、リソースを確実に解放するための構文です。

5-3. usingとtry-catchは役割が違う

usingtry-catchは混同されやすいですが、役割は明確に違います。

try-catchは、例外が発生したときに処理するためのものです。

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

一方、usingは、リソースを使い終わったあとに解放するためのものです。

C#
using (StreamReader reader = new StreamReader("sample.txt"))
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}

usingを書いたからといって、例外が自動的にcatchされるわけではありません。
usingの中で例外が発生した場合、その例外を処理したいならtry-catchが必要です。

5-4. usingの中で例外が起きた場合の挙動

usingの中で例外が発生した場合でも、Dispose()は呼び出されます。

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

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

Console.WriteLine(number);
}

このコードでは、int.Parse("abc")で例外が発生します。
そのため、Console.WriteLine(number);は実行されません。

しかし、usingブロックを抜けるときに、reader.Dispose()は呼び出されます。

ただし、例外そのものは処理されていません。
例外を受け止めたい場合は、次のようにtry-catchを組み合わせます。

C#
try
{
using (StreamReader reader = new StreamReader("sample.txt"))
{
string text = reader.ReadToEnd();
int number = int.Parse(text);

Console.WriteLine(number);
}
}
catch (FormatException)
{
Console.WriteLine("ファイルの内容が数値ではありません。");
}
catch (IOException)
{
Console.WriteLine("ファイル処理でエラーが発生しました。");
}

このように、usingはリソース解放を担当し、try-catchは例外処理を担当します。

5-5. usingにtry-catchを組み合わせる基本パターン

usingtry-catchを組み合わせる基本パターンは、tryの中にusingを書く形です。

C#
try
{
using (StreamReader reader = new StreamReader("sample.txt"))
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (IOException ex)
{
Console.WriteLine("ファイルの読み込みに失敗しました。");
Console.WriteLine(ex.Message);
}

この書き方では、ファイルのオープン、読み込み、リソース解放を安全に扱えます。

usingによって、ファイルの読み込み中に例外が発生しても、StreamReaderは適切に破棄されます。
そして、発生した例外は外側のcatchで処理できます。

実務でも、このパターンはよく使われます。

5-6. try-catchはusingの外側・内側どちらに書くべきか

try-catchusingの外側に書くか、内側に書くかは、どこで発生する例外を処理したいかによって変わります。

一般的には、usingの外側にtry-catchを書くことが多いです。

C#
try
{
using (StreamReader reader = new StreamReader("sample.txt"))
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
}
catch (Exception ex)
{
Console.WriteLine("ファイル処理でエラーが発生しました。");
Console.WriteLine(ex.Message);
}

この形なら、StreamReaderの作成時にファイルが見つからない場合も、読み込み中にエラーが起きた場合も、まとめてcatchできます。

一方、usingの内側にtry-catchを書くと、usingブロックに入ったあとの処理だけを細かく制御できます。

C#
using (StreamReader reader = new StreamReader("sample.txt"))
{
try
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
catch (IOException ex)
{
Console.WriteLine("読み込み中にエラーが発生しました。");
Console.WriteLine(ex.Message);
}
}

ただし、この場合、new StreamReader("sample.txt")の時点で発生する例外は、内側のcatchでは捕捉できません。

初心者のうちは、まずtryの中にusingを書く形を覚えるとよいでしょう。

C#
try
{
using (var resource = new SomeResource())
{
// リソースを使う処理
}
}
catch (Exception ex)
{
// 例外処理
}

5-7. using宣言とtry-catchを併用する書き方

C#では、usingステートメントのほかに、using宣言という書き方もあります。

C#
using StreamReader reader = new StreamReader("sample.txt");

string text = reader.ReadToEnd();
Console.WriteLine(text);

using宣言では、変数が宣言されたスコープを抜けるときに、自動的にDispose()が呼ばれます。

try-catchと組み合わせる場合は、次のように書けます。

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

string text = reader.ReadToEnd();
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (IOException ex)
{
Console.WriteLine("ファイル処理でエラーが発生しました。");
Console.WriteLine(ex.Message);
}

この書き方は、波かっこが少なくなり、コードがすっきりします。

ただし、using宣言はスコープの終わりで破棄されるため、どこまでがリソースの有効範囲なのかを意識する必要があります。

短い処理であればusing宣言は便利ですが、リソースの使用範囲を明確に見せたい場合は、従来のusingステートメントを使うほうが読みやすいこともあります。

6. try-catchを使うべき場面・使わないほうがよい場面

try-catchは便利ですが、すべての処理に書けばよいわけではありません。適切な場面で使わないと、コードが読みにくくなったり、本当の原因がわかりにくくなったりします。

ここでは、try-catchを使うべき場面と、使わないほうがよい場面を整理します。

6-1. 使うべき場面|ファイル操作・通信・DB・外部API

try-catchを使うべき代表的な場面は、外部環境に依存する処理です。

たとえば、次のような処理です。

C#
// ファイル操作
File.ReadAllText("data.txt");

// 通信
await httpClient.GetAsync("https://example.com");

// データベース接続
connection.Open();

// 外部API呼び出し
await service.GetDataAsync();

これらの処理は、プログラムの中だけで完結しません。

ファイルが存在しないかもしれません。
ネットワークが切断されているかもしれません。
データベースが停止しているかもしれません。
外部APIがエラーを返すかもしれません。

このような処理では、例外が発生することを前提にしてtry-catchを使う必要があります。

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

外部要因で失敗する可能性がある処理は、try-catchの重要な使いどころです。

6-2. 使うべき場面|ユーザー入力や変換処理

ユーザー入力や変換処理でも、例外が発生することがあります。

C#
try
{
int age = int.Parse(input);
Console.WriteLine($"年齢: {age}");
}
catch (FormatException)
{
Console.WriteLine("年齢は数字で入力してください。");
}

ユーザーが必ず正しい形式で入力するとは限りません。
そのため、入力値を数値や日付に変換する処理では、例外に備える必要があります。

ただし、C#ではint.TryParseDateTime.TryParseのように、変換できるかどうかを事前に判定するメソッドもあります。

C#
if (int.TryParse(input, out int age))
{
Console.WriteLine($"年齢: {age}");
}
else
{
Console.WriteLine("年齢は数字で入力してください。");
}

ユーザー入力のように失敗がよく起こる処理では、try-catchよりもTryParseを使うほうが自然な場合があります。

6-3. 使わないほうがよい場面|事前チェックで防げる処理

try-catchは、事前チェックで防げる処理には向いていないことがあります。

たとえば、配列の範囲外アクセスは、インデックスを確認すれば防げます。

よくない例は次のとおりです。

C#
try
{
Console.WriteLine(numbers[index]);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("範囲外です。");
}

この場合は、例外が起きてから対応するより、先に範囲を確認するほうがわかりやすいです。

C#
if (index >= 0 && index < numbers.Length)
{
Console.WriteLine(numbers[index]);
}
else
{
Console.WriteLine("範囲外です。");
}

同じように、nullかどうか確認できる場合は、先にif文でチェックするほうが適切です。

C#
if (user != null)
{
Console.WriteLine(user.Name);
}
else
{
Console.WriteLine("ユーザー情報がありません。");
}

事前に簡単に防げる処理までtry-catchに頼ると、コードの意図がわかりにくくなります。

6-4. 例外処理に頼りすぎるとコードが読みにくくなる理由

try-catchを使いすぎると、コードが読みにくくなることがあります。

たとえば、あらゆる処理を大きなtryで囲んでしまうと、どの行でどの例外が発生する可能性があるのかわかりにくくなります。

C#
try
{
// 入力処理
// 変換処理
// ファイル読み込み
// データベース保存
// メール送信
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

このようなコードでは、エラーが発生したときに原因を特定しにくくなります。

また、catchの中で何でも処理しようとすると、正常な処理とエラー処理が混ざり、コード全体の見通しが悪くなります。

try-catchは必要な場所に絞って使うことが大切です。

6-5. if文で防ぐべきケースとtry-catchで扱うべきケース

if文で防ぐべきか、try-catchで扱うべきかは、次のように考えると判断しやすいです。

事前に簡単に確認できるものは、if文で防ぎます。

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

範囲チェックもif文が向いています。

C#
if (index >= 0 && index < list.Count)
{
Console.WriteLine(list[index]);
}

一方、外部環境に依存していて、事前チェックしても確実には防げないものはtry-catchで扱います。

C#
try
{
string text = File.ReadAllText(filePath);
}
catch (IOException ex)
{
Console.WriteLine("ファイルの読み込みに失敗しました。");
Console.WriteLine(ex.Message);
}

ファイルが存在するかを事前に確認しても、その直後に削除される可能性があります。
通信先の状態も、実際に接続してみるまでわからないことがあります。

このようなケースでは、try-catchで例外に備えるのが適切です。

7. C#のtry-catchでよくある間違い

try-catchは便利ですが、使い方を間違えると、かえって不具合の原因になります。ここでは、C#初心者がやりがちなtry-catchの間違いを紹介します。

7-1. catchで何も処理しない

最も避けたい書き方のひとつが、catchで何もしないことです。

C#
try
{
int number = int.Parse("abc");
}
catch
{
}

このコードでは、例外が発生しても何も表示されず、ログも残りません。
そのため、問題が起きているのに気づけなくなります。

最低限、エラーメッセージを表示したり、ログを残したりするべきです。

C#
try
{
int number = int.Parse("abc");
}
catch (FormatException ex)
{
Console.WriteLine("数値変換に失敗しました。");
Console.WriteLine(ex.Message);
}

例外を無視すると、あとから原因を調査するのが非常に難しくなります。

7-2. すべての例外をExceptionだけで受ける

すべての例外をExceptionだけで受ける書き方も、初心者がよくやりがちです。

C#
try
{
string text = File.ReadAllText("sample.txt");
int number = int.Parse(text);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

この書き方でも例外は捕捉できますが、何の例外を想定しているのかがわかりにくくなります。

できるだけ具体的な例外型でcatchしたほうが、処理の意図が明確になります。

C#
try
{
string text = File.ReadAllText("sample.txt");
int number = int.Parse(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (FormatException)
{
Console.WriteLine("ファイルの内容が数値ではありません。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}

最後にExceptionを書くのは、予期しない例外に備える目的であれば有効です。
しかし、最初からすべてをExceptionだけで済ませるのは避けたほうがよいです。

7-3. 例外を握りつぶして原因がわからなくなる

例外を握りつぶすとは、例外が発生したのに何も対応せず、なかったことにしてしまうことです。

C#
try
{
SaveData();
}
catch
{
// 何もしない
}

このようなコードでは、保存に失敗してもユーザーにも開発者にもわかりません。

結果として、「保存したはずなのにデータがない」という不具合につながります。

例外を処理する場合は、少なくとも次のいずれかを行うべきです。

C#
try
{
SaveData();
}
catch (IOException ex)
{
Console.WriteLine("データの保存に失敗しました。");
Console.WriteLine(ex.Message);
}

実務では、画面表示だけでなくログ出力も重要です。

C#
catch (IOException ex)
{
Console.WriteLine("データの保存に失敗しました。");

// 例: logger.LogError(ex, "データ保存に失敗しました。");
}

例外を握りつぶすと、問題の発見と解決が遅れます。

7-4. tryブロックの範囲が広すぎる

tryブロックの範囲が広すぎるのも、よくある間違いです。

C#
try
{
string input = Console.ReadLine();
int number = int.Parse(input);
string text = File.ReadAllText("sample.txt");
SaveToDatabase(number, text);
SendMail();
}
catch (Exception ex)
{
Console.WriteLine("エラーが発生しました。");
Console.WriteLine(ex.Message);
}

このコードでは、入力、変換、ファイル読み込み、DB保存、メール送信のすべてが1つのtryに入っています。

これでは、どの処理でエラーが起きたのかがわかりにくくなります。

必要に応じて、例外が発生しやすい処理ごとに分けると読みやすくなります。

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

tryブロックは、できるだけ必要な範囲に絞るのが基本です。

7-5. catchの順番を間違える

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

正しい順番は次のとおりです。

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

逆に、先にExceptionを書いてしまうと、より具体的な例外に到達できません。

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

FormatExceptionExceptionの一種です。
そのため、先にExceptionで捕捉されてしまいます。

catchは「具体的な例外から一般的な例外へ」の順番で書きましょう。

7-6. 例外処理の中でさらに例外を起こす

catchブロックの中で、さらに別の例外を起こしてしまうこともあります。

C#
try
{
int number = int.Parse("abc");
}
catch (Exception ex)
{
string message = null;
Console.WriteLine(message.Length);
}

このコードでは、最初にint.Parseで例外が発生します。
しかし、catchの中でmessage.Lengthを呼び出しているため、さらに別の例外が発生します。

例外処理の中では、できるだけ安全な処理を書くことが大切です。

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

ログ出力やエラーメッセージ表示の処理でも、失敗する可能性がある場合は注意が必要です。

7-7. throw ex; と throw; の違いを理解していない

例外を再スローするときに、throw ex;と書いてしまうのもよくある間違いです。

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

この書き方では、例外のスタックトレースがリセットされ、元の発生場所を追跡しにくくなる場合があります。

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

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

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

実務では、ログを残してから上位の処理に例外を任せたい場面があります。そのような場合は、throw ex;ではなくthrow;を使うのが基本です。

8. 実務で意識したいtry-catchのベストプラクティス

C#のtry-catchは、書き方を覚えるだけでなく、実務でどう使うかが重要です。ここでは、実際の開発で意識したいベストプラクティスを紹介します。

8-1. 捕捉する例外はできるだけ具体的にする

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

たとえば、数値変換のエラーを処理したいならFormatExceptionを使います。

C#
try
{
int number = int.Parse(input);
}
catch (FormatException)
{
Console.WriteLine("数値の形式が正しくありません。");
}

ファイルが見つからない場合を処理したいならFileNotFoundExceptionを使います。

C#
try
{
string text = File.ReadAllText("sample.txt");
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}

具体的な例外を使うと、コードを読んだ人が「この処理では何の失敗を想定しているのか」を理解しやすくなります。

最後に予期しない例外を扱うためにExceptionを置くことはありますが、最初から何でもExceptionだけで受けるのは避けましょう。

8-2. ログを残して原因を追跡できるようにする

実務では、例外が発生したときにログを残すことがとても重要です。

画面に「エラーが発生しました」と表示するだけでは、あとから原因を調査できません。

C#
try
{
SaveData();
}
catch (IOException ex)
{
Console.WriteLine("保存に失敗しました。");

// 実務ではログ出力を行う
// logger.LogError(ex, "データ保存中にエラーが発生しました。");
}

ログには、例外メッセージ、スタックトレース、処理対象の情報などを残すと調査しやすくなります。

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

ただし、パスワード、アクセストークン、個人情報などをログに出力しないよう注意が必要です。

8-3. ユーザーに見せるメッセージと開発者向け情報を分ける

例外情報をそのままユーザーに見せるのは、あまりよくありません。

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

ex.ToString()には、例外の詳細情報や内部的なクラス名、ファイルパスなどが含まれることがあります。
これは開発者には役立ちますが、一般ユーザーにはわかりにくく、場合によってはセキュリティ上の問題にもなります。

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

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

// 開発者向けにはログに詳細を残す
// logger.LogError(ex, "処理中に例外が発生しました。");
}

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

8-4. 必要な場所だけtry-catchで囲む

try-catchは、必要な場所だけに書きましょう。

よくない例は、メソッド全体を大きなtry-catchで囲む書き方です。

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

これでは、どの処理でどの例外が起きたのかがわかりにくくなります。

例外が発生する可能性が高い処理や、個別に対応したい処理だけを囲むほうが読みやすくなります。

C#
try
{
string text = File.ReadAllText("data.txt");
SaveDatabase(text);
}
catch (IOException ex)
{
Console.WriteLine("ファイル処理に失敗しました。");
Console.WriteLine(ex.Message);
}

tryの範囲を小さくすると、例外の原因を特定しやすくなります。

8-5. 例外を再スローする場合の正しい書き方

例外を捕捉したあと、自分では完全に処理せず、上位の呼び出し元に任せたい場合があります。

その場合は、ログを残してからthrow;で再スローします。

C#
try
{
ProcessData();
}
catch (Exception ex)
{
Console.WriteLine("ProcessDataで例外が発生しました。");
Console.WriteLine(ex.Message);

throw;
}

throw;を使うことで、元の例外情報を保ったまま上位へ伝えられます。

一方、throw ex;は避けましょう。

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

この書き方では、例外の発生位置を追跡しにくくなることがあります。

例外を再スローするときは、基本的にthrow;を使うと覚えておきましょう。

8-6. using・finally・Disposeを適切に使い分ける

C#では、リソース解放のためにusingfinallyDisposeを使います。

ファイルやDB接続など、IDisposableを実装しているオブジェクトは、使い終わったらDispose()を呼ぶ必要があります。

最もよく使われるのはusingです。

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

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

usingを使えば、例外が発生してもスコープを抜けるときに自動的にDispose()が呼ばれます。

一方、finallyは、usingでは表現しにくい後片付けをしたい場合に使います。

C#
try
{
Console.WriteLine("処理開始");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("必ず実行したい終了処理");
}

基本的には、リソース解放にはusing、例外の有無に関係なく実行したい処理にはfinally、手動で解放が必要な場合にはDispose()を使うと考えるとよいです。

9. async/awaitでtry-catchを使う場合の注意点

C#では、非同期処理を書くときにasyncawaitを使います。非同期処理でもtry-catchは使えますが、同期処理とは少し注意点があります。

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

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

C#
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
try
{
using HttpClient client = new HttpClient();

string result = await client.GetStringAsync("https://example.com");
Console.WriteLine(result);
}
catch (HttpRequestException ex)
{
Console.WriteLine("通信エラーが発生しました。");
Console.WriteLine(ex.Message);
}
}
}

このコードでは、API通信中にエラーが発生した場合、HttpRequestExceptioncatchします。

awaitしている処理で例外が発生すると、その例外はcatchで捕捉できます。

9-2. awaitしないと例外をcatchできないケースがある

非同期処理で注意したいのが、awaitしないと例外をその場でcatchできない場合があることです。

たとえば、次のようなコードを見てください。

C#
try
{
Task task = DoAsync();
}
catch (Exception ex)
{
Console.WriteLine("例外をcatchしました。");
Console.WriteLine(ex.Message);
}

このコードでは、DoAsync()の中で非同期的に例外が発生しても、awaitしていないため、このcatchで期待どおりに捕捉できないことがあります。

正しくは、次のようにawaitします。

C#
try
{
await DoAsync();
}
catch (Exception ex)
{
Console.WriteLine("例外をcatchしました。");
Console.WriteLine(ex.Message);
}

async/awaitを使う場合は、例外処理したい非同期処理をきちんとawaitすることが重要です。

9-3. asyncメソッドでのtry-catchのサンプルコード

次は、非同期メソッドでtry-catchを使うサンプルです。

C#
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
await FetchDataAsync();
}

static async Task FetchDataAsync()
{
try
{
using HttpClient client = new HttpClient();

string url = "https://example.com";
string content = await client.GetStringAsync(url);

Console.WriteLine(content);
}
catch (HttpRequestException ex)
{
Console.WriteLine("HTTP通信に失敗しました。");
Console.WriteLine(ex.Message);
}
catch (TaskCanceledException ex)
{
Console.WriteLine("通信がタイムアウト、またはキャンセルされました。");
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}
}
}

このコードでは、HTTP通信に関する例外を個別にcatchしています。

通信処理では、サーバーが応答しない、URLが間違っている、ネットワークが切断される、タイムアウトするなど、さまざまな問題が起こります。

そのため、async/awaitを使ったAPI通信では、try-catchを適切に書くことが大切です。

9-4. Task.RunやAPI通信で例外処理が必要になる場面

Task.Runを使う場合も、awaittry-catchを組み合わせることで例外を捕捉できます。

C#
try
{
await Task.Run(() =>
{
throw new InvalidOperationException("処理に失敗しました。");
});
}
catch (InvalidOperationException ex)
{
Console.WriteLine("非同期処理で例外が発生しました。");
Console.WriteLine(ex.Message);
}

このコードでは、Task.Runの中で発生した例外を、外側のcatchで捕捉しています。

ただし、次のようにawaitしない場合は注意が必要です。

C#
try
{
Task.Run(() =>
{
throw new InvalidOperationException("処理に失敗しました。");
});
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}

この場合、Task.Runの中で発生した例外は、直後のcatchでは期待どおりに捕捉できません。

API通信、ファイルの非同期読み込み、DBの非同期処理などでは、awaittry-catchを正しく組み合わせることが重要です。

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

ここでは、C#のtry-catchについて初心者が疑問に感じやすいポイントを解説します。

10-1. try-catchはどこに書くのが正しい?

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

たとえば、ファイル読み込みに失敗したときに、その場でメッセージを表示したいなら、ファイル読み込み処理をtry-catchで囲みます。

C#
try
{
string text = File.ReadAllText("sample.txt");
Console.WriteLine(text);
}
catch (IOException ex)
{
Console.WriteLine("ファイルの読み込みに失敗しました。");
Console.WriteLine(ex.Message);
}

一方、そのメソッドでは対応せず、呼び出し元に任せたい場合は、無理にcatchしないこともあります。

try-catchは「書ける場所に全部書く」のではなく、「その場で例外に対応する必要がある場所」に書くのが基本です。

10-2. catchは複数書いてもよい?

はい、catchは複数書いても問題ありません。

むしろ、例外の種類ごとに処理を分けたい場合は、複数のcatchを書くのが一般的です。

C#
try
{
string text = File.ReadAllText("sample.txt");
int number = int.Parse(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (FormatException)
{
Console.WriteLine("ファイルの内容が数値ではありません。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}

ただし、catchの順番には注意が必要です。
具体的な例外を先に書き、Exceptionのような広い例外は最後に書きます。

10-3. finallyは必ず必要?

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

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

C#
try
{
Console.WriteLine("処理を実行します。");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("終了処理を実行します。");
}

ファイルやDB接続などのリソース解放では、finallyを使うこともあります。
ただし、C#ではusingを使うことで、リソース解放をより簡潔に書ける場合が多いです。

後片付けが不要な処理であれば、finallyは書かなくても問題ありません。

10-4. usingを使えばtry-catchは不要?

usingを使っても、try-catchが不要になるわけではありません。

usingは、リソースを自動的に解放するための構文です。
try-catchは、例外を捕捉して処理するための構文です。

役割が違います。

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

string text = reader.ReadToEnd();
Console.WriteLine(text);
}
catch (IOException ex)
{
Console.WriteLine("ファイル処理でエラーが発生しました。");
Console.WriteLine(ex.Message);
}

このコードでは、usingStreamReaderの破棄を担当し、try-catchが例外処理を担当しています。

usingは例外を自動的に処理してくれるわけではないため、例外に対応したい場合はtry-catchも必要です。

10-5. Exceptionをcatchするのは悪いこと?

Exceptioncatchすること自体が必ず悪いわけではありません。

ただし、何でもかんでもExceptionで受けると、どの例外を想定しているのかがわかりにくくなります。

よい書き方は、まず具体的な例外をcatchし、最後に必要であればExceptionを置く形です。

C#
try
{
int number = int.Parse("abc");
}
catch (FormatException)
{
Console.WriteLine("数値の形式が正しくありません。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}

アプリケーションの入口や共通エラーハンドリングでは、最後の安全網としてExceptionを捕捉することがあります。

大切なのは、Exceptionを使う目的を明確にすることです。

10-6. エラーを画面に表示するだけでよい?

学習用の簡単なプログラムであれば、エラーを画面に表示するだけでも十分な場合があります。

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

しかし、実務では画面表示だけでは不十分なことが多いです。

ユーザーがエラー画面を閉じてしまうと、開発者は原因を調査できません。
そのため、実務ではログを残すことが重要です。

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

// 実務ではログに詳細を記録する
// logger.LogError(ex, "処理中に例外が発生しました。");
}

ユーザーにはわかりやすいメッセージを表示し、開発者向けには詳細なログを残す。
この分け方を意識すると、保守しやすいC#アプリケーションになります。

まとめ

C#のtry-catchは、実行時に発生する例外へ対応するための基本的な構文です。

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

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

初心者のうちは、まずこの基本形を理解することが大切です。

ただし、実務ではすべての例外をExceptionだけで受けるのではなく、FormatExceptionFileNotFoundExceptionIOExceptionHttpRequestExceptionなど、できるだけ具体的な例外型を使うことが重要です。

また、try-catchとあわせてfinallyusingも理解しておきましょう。

finallyは、例外の有無に関係なく最後に実行したい処理を書くための構文です。
usingは、ファイルやDB接続などのリソースを自動的に解放するための構文です。
try-catchは例外処理、usingはリソース解放というように、役割が違います。

特に、C#でファイル操作、通信、データベース、外部API、ユーザー入力、変換処理を扱う場合、try-catchは欠かせません。

一方で、何でもtry-catchで囲めばよいわけではありません。
配列の範囲チェックやnullチェックのように、事前にif文で防げるものは、try-catchではなく通常の条件分岐で対応するほうが読みやすくなります。

C#の例外処理では、次のポイントを意識しましょう。

tryブロックは必要な範囲だけにする。
catchでは何もせずに握りつぶさない。
捕捉する例外はできるだけ具体的にする。
ユーザー向けメッセージと開発者向けログを分ける。
リソース解放にはusingfinallyを適切に使う。
例外を再スローする場合はthrow;を使う。
非同期処理ではawaittry-catchを正しく組み合わせる。

try-catchは、C#で安全で保守しやすいプログラムを書くための重要な基礎です。基本構文だけでなく、使うべき場面と使わないほうがよい場面を理解し、適切な例外処理を書けるようにしていきましょう。