1. はじめに
プログラムがHello, world!よりちょっと複雑になると、すぐに「エラーの種類ごとに違う処理したいな」ってなるよね。例えばファイル操作、ネットワーク、DBを使ってるとき、それぞれのエラーで違う対応が必要。ファイルがなければユーザーに別のファイル選ばせたり、ネットワークエラーなら「もう一回やってみて」とか「ケーブル確認して、また猫がかじったかも」みたいなメッセージを出したり。
C#では複数のcatchブロックを連続で書くことでこれを実現するよ。それぞれのブロックは特定の例外タイプ(とその子孫)に特化してる。
複数catchの構造
try
{
// ここに色んな例外が発生するかもしれないコードを書く
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"ファイルが見つからない: {ex.Message}");
}
catch (IOException ex) // FileNotFoundException以外のI/Oエラーをキャッチ
{
Console.WriteLine($"入出力エラー: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"なんかおかしい: {ex.Message}");
}
重要! コンパイラは上から下にcatchブロックを見て、最初に合うやつで止まる。FileNotFoundExceptionがキャッチされたら、その下のcatchにはもう行かないよ。
「チェーン」のイラスト
| 例外タイプ | どのブロックがキャッチ? |
|---|---|
| FileNotFoundException | 最初のcatch |
| IOException(他) | 2番目のcatch |
| ArgumentException | 3番目(共通)のcatch |
なぜ混ぜちゃダメ?
一番「広い」catch、例えばcatch (Exception)は一番最後に書こう。そうしないと全部の例外が早めに吸収されて、細かいcatchまで到達しなくなる。
これは、トースターが焦げて火災報知器が鳴ったからって、全部の火災報知器をオフにしちゃうのと同じ。次の火事はシステムが気づかないよ。
実用例
using System;
using System.IO;
class Program
{
static void Main()
{
try
{
double result = CalculateAverageAgeFromFile("users.txt");
Console.WriteLine($"平均年齢 — {result}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("エラー: ファイルが見つからない。パスを確認してね。");
}
catch (FormatException ex)
{
Console.WriteLine("データエラー: 年齢が読み取れない。");
}
catch (Exception ex)
{
Console.WriteLine($"その他のエラー: {ex.Message}");
}
}
static double CalculateAverageAgeFromFile(string filePath)
{
// (実装:ファイルを読んで、年齢をパースして、平均を計算)
// ...
throw new NotImplementedException();
}
}
ここでは、ファイルがない場合(FileNotFoundException)と、ファイルの中身が「変」な場合(FormatException)で処理を分けてる。他のケースは「予備」の3つ目のブロックでキャッチ。
2. catchフィルター:細かいニュアンスをキャッチ
複数のcatchブロックは便利だけど、それだけじゃ足りないときもある。同じ例外タイプでも状況によって違う対応をしたいことがあるよね。
例えば、ネットが切れてる理由が「ネットが切れた」なら再接続を試すし、サーバーが応答しないだけなら別のメッセージを出したいとか。
そこで登場するのがcatchフィルター。C#のスーパー機能で、タイプだけじゃなく追加条件でも例外をキャッチできる。
whenフィルターの書き方
catch (IOException ex) when (ex.Message.Contains("ディスクがない"))
{
Console.WriteLine("おっと!たぶんUSBメモリ抜いちゃったね。");
}
catch (IOException ex)
{
Console.WriteLine("他の入出力エラー: " + ex.Message);
}
ここでは最初のブロックがIOExceptionのうち、メッセージに"ディスクがない"が含まれてるやつだけキャッチ。他は2番目のブロックへ。
リアルな使い方
フィルターは特にネットワークエラーのとき便利。例外の中身を見て、再試行するか、エラーを伝えるか決められる。
もう一つ例:ファイルをパースするメソッドで、ただのFormatExceptionじゃなくて、年齢のパースエラー(例えば"abc"みたいな文字列が年齢欄にある)だけを分けて処理したいとき。
catch (FormatException ex) when (ex.Message.Contains("年齢"))
{
Console.WriteLine("エラー: 年齢が読み取れなかった。データを確認してね。");
}
catch (FormatException ex)
{
Console.WriteLine("データ形式エラー: " + ex.Message);
}
フィルターとパフォーマンス
フィルターは便利だけど、catchブロックに入る前に条件が評価されるから、条件が合わなければブロックの中身は全く実行されない。
ちなみに、フィルターの条件式(whenの中)で例外が発生した場合(例えばゼロ除算とか)、そのcatchブロックでは絶対キャッチされないから注意してね。
3. 複数catchとフィルターの組み合わせ
例えば、プロジェクトで例外のタイプだけじゃなくて中身も見て処理を分けたいとき。IOExceptionで、アクセス権の問題かディスク容量不足かで対応を変えたい場合とか。
try
{
File.AppendAllText("log.txt", "新しいログ\n");
}
catch (IOException ex) when (ex.Message.Contains("空き容量がない"))
{
Console.WriteLine("エラー: ディスクの空き容量がなくなった!");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("エラー: ファイル書き込み権限がない。管理者で実行してね。");
}
catch (IOException ex)
{
Console.WriteLine("他のディスクエラー: " + ex.Message);
}
ここではフィルターも使って、エラータイプごとにも分けてる。こういう柔軟さは複雑なアプリで、よくあるトラブルにちゃんと対応したいときに超便利。
4. フィルターと複数catchの特徴と落とし穴
- catchフィルターでは例外変数(ex)を使えるけど、値は変えられない。
- whenの中で例外を投げると、そのcatchでは絶対キャッチされず、上に伝播する。
- フィルターのロジックも契約の一部になるから、プロジェクトの仲間にも「全部のIOExceptionがキャッチされるわけじゃなくて、条件次第だよ」って伝えておこう。
- メソッド全体でcatchが1個だけで、その中にフィルターがある場合、余計な例外をキャッチし損ねることもある。迷ったら明示的に分けて書こう。
- 大きなアプリでは、フィルターでクリティカルなエラーだけログに残すみたいな集中管理もできる。
5. 面接や実務でのシナリオ
フィルターや複数catchの知識は、ただの「きれいなコード」やclean codeの点数稼ぎだけじゃなくて、実際に大規模で複雑な分散アプリのサポートをするポジションの面接でも求められるリアルなスキルだよ。
質問例:
- C#でファイル読み込みエラーの色んなケース(ファイルなし、ディスク使用中、データ破損)ごとに違うメッセージを出すには?
- 共通のcatch (Exception)ブロックを置いたとき、大事なエラーを「握りつぶさない」ためには?
- catchフィルターって何のため?その中でthrowできる?
GO TO FULL VERSION