ファイル操作やネットワーク通信などの処理では、一つの操作に対して複数の異なるエラー要因が考えられます。例えば、ファイルを読み込む際、「ファイルが存在しない場合」と「アクセス権限がない場合」では、ユーザーへの案内やシステムが取るべきリカバリ対応は異なります。
C#のtry-catch構文では、複数のcatchブロックを記述することで、発生した例外の型に応じたきめ細やかなエラーハンドリングが可能です。ここでは、設定ファイルの読み込み処理を題材に、適切な例外の振り分け実装について解説します。
複数のcatchブロックによる例外の振り分け
例外処理において重要な原則は、**「具体的(詳細)な例外から先に記述し、汎用的な例外は後に記述する」**ということです。
C#の例外クラスは継承関係を持っており、親クラス(例:Exception)で捕捉すると、その子クラスの例外もすべて含まれてしまいます。そのため、先に親クラスを記述すると、それ以降の具体的な例外処理(子クラスのcatchブロック)には到達できなくなります(コンパイルエラーになります)。
実践的なコード例:設定ファイルの読み込み
以下のコードは、アプリケーションの設定ファイルを読み込む処理です。「ファイルが見つからない」「ディレクトリがない」「権限がない」「その他のIOエラー」という4つのケースを区別して処理しています。
using System;
using System.IO;
namespace ConfigurationManager
{
class Program
{
static void Main()
{
// シナリオ:
// アプリケーション起動時に重要設定ファイル(settings.xml)を読み込む。
// 発生するエラーに応じて、ユーザーへのメッセージを変える必要がある。
string filePath = @"Configs\settings.xml";
try
{
// ファイル読み込みを試行
// ここで様々なIOエラーが発生する可能性があります。
string content = File.ReadAllText(filePath);
Console.WriteLine("設定ファイルの読み込みに成功しました。");
Console.WriteLine($"内容: {content}");
}
// 1. 最も具体的な例外:ファイル自体が存在しない
catch (FileNotFoundException ex)
{
Console.WriteLine("[エラー] 設定ファイルが見つかりません。");
Console.WriteLine($"ファイル名: {ex.FileName}");
// リカバリ案: デフォルト設定を生成する処理などをここに記述
}
// 2. 具体的な例外:パスに含まれるディレクトリが存在しない
catch (DirectoryNotFoundException)
{
Console.WriteLine("[エラー] 設定フォルダ(Configs)が存在しません。");
// リカバリ案: フォルダを作成する処理などをここに記述
}
// 3. 具体的な例外:ファイルへのアクセス権限がない
catch (UnauthorizedAccessException)
{
Console.WriteLine("[エラー] ファイルへのアクセス権限がありません。管理者として実行してください。");
}
// 4. やや汎用的な例外:その他のIOエラー(ディスクフル、使用中など)
catch (IOException ex)
{
Console.WriteLine($"[エラー] ファイル読み込み中にIOエラーが発生しました: {ex.Message}");
}
// 5. 最も汎用的な例外:予期せぬエラーすべて
catch (Exception ex)
{
Console.WriteLine($"[重大なエラー] 予期せぬ問題が発生しました: {ex.Message}");
}
}
}
}
実行結果の例(ファイルがない場合)
[エラー] 設定ファイルが見つかりません。
ファイル名: settings.xml
技術的なポイントと注意点
1. 継承関係と記述順序
上記のコードで登場する例外クラスの一部は、以下のような継承関係にあります。
ObjectException(全ての例外の基底)SystemExceptionIOExceptionFileNotFoundExceptionDirectoryNotFoundException
もし、catch (IOException)をcatch (FileNotFoundException)より上に書いてしまうと、ファイルが見つからないエラーもIOExceptionブロックで捕捉されてしまい、個別の処理が行えません。コンパイラが到達不能コードとして警告またはエラーを出す場合もありますが、基本ルールとして**「派生クラス(子)を上、基底クラス(親)を下」**と覚えておく必要があります。
2. 例外フィルタ (when句) の活用
C# 6.0以降では、whenキーワードを使用することで、同じ例外型でも特定の条件(プロパティの値など)に基づいて捕捉するかどうかをフィルタリングできます。
catch (IOException ex) when (ex.Message.Contains("Disk full"))
{
Console.WriteLine("ディスク容量が不足しています。");
}
これにより、型による分岐だけでは対応しきれない細かな制御が可能になります。
まとめ
複数の例外を適切に捕捉・分類することは、堅牢なアプリケーションを作成するために不可欠です。すべてのエラーを単にExceptionでまとめて処理してしまうと、ユーザーは何が原因で失敗したのか理解できず、適切な対処ができなくなります。エラーの原因を明確にし、状況に応じたリカバリ処理を提供するために、このパターンを活用してください。
