1. はじめに
ファイル操作はほとんど常にプログラム外の状況に左右されます: ファイルが削除されているかもしれない、別のプログラムにロックされているかもしれない、ユーザーの権限が足りないかもしれない、ディスクの空きがなくなるかもしれない。これらは例外につながります—処理が中断されて例外オブジェクト(Exception)が「投げられる」特別な状況です。こうした例外をキャッチして処理しなければ、プログラムはエラーで終了し、ユーザーにコンソールの赤い怖いテキストを見せる(または、さらに悪く、黙ってクラッシュする)ことになります。
C# ではこのような状況の処理に try-catch ブロック(簡単に言えば「エラーのトラップ」)を使います。これによりエラーを「捕まえる」だけでなく、その後どうするかを落ち着いて決められます: フレンドリーなメッセージを表示する、別のファイルを選ばせる、ログに書き込む、あるいは単に再試行するなど。
実際のアプリケーションでは
もしコンソール電卓を作っているならファイル操作のエラーを全部処理しなくてもなんとかなるかもしれません。しかし、企業向けのドキュメント処理アプリやセーブ機能のあるゲームを作るなら、こうしたエラーを無視するのは命綱なしで綱渡りするようなものです: いつか必ず問題が起きます。
2. 例外処理の復習
基本構文 try-catch
ファイルに関する具体例に入る前に、既に例外の講義で見た try-catch の基本構文を思い出しましょう:
try
{
// ここに例外を"throw"する可能性のあるコード
}
catch (Exception ex)
{
// ここで全ての可能な例外をキャッチしています...でもこれはあまりおすすめしません!
Console.WriteLine("何かがうまくいかなかった: " + ex.Message);
}
try の中には潜在的に危険なコードを書き、catch の中で何が起きたときにどうするかを記述します。
プログラマのジョーク: "try-catch はエラー用のデジタル不可視マントみたいなもの。エラーはあるけど見えない!"
ファイルに関する例外
.NET を使ってファイルを扱うとき、よく出会う例外があります — 例えばストリームの作成、読み書きのときなど。典型的なシナリオと関連する例外をいくつか挙げます:
- ファイルが見つからない → FileNotFoundException
- ファイル/ディレクトリへのアクセスが拒否される → UnauthorizedAccessException
- パスの問題(不正な文字、パスが長すぎるなど) → PathTooLongException, ArgumentException
- ファイルが別プロセスで使用中 → IOException
- ディスクの空き容量不足 → IOException
ファイル操作に関連する例外は多くが IOException の派生です。これらはすべて基底型 System.Exception を継承しています。
3. 実践: ファイル操作時のエラーをキャッチして処理する
ここで「成長中のアプリ」を例に典型的なケースを見てみましょう: ファイルから挨拶を読み取って処理し、ユーザーに表示することを試みるとします。ここに try-catch を使ったエラーチェックを追加します。
例 1: "ファイルが見つからない" の処理
string filePath = "hello.txt";
try
{
string greeting = File.ReadAllText(filePath);
Console.WriteLine("ファイルの内容: " + greeting);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("ファイルが見つかりません! ファイル " + filePath + " が存在するか確認してください。");
// 詳細を追加で表示することもできます
Console.WriteLine("技術的な詳細: " + ex.Message);
}
ファイル "hello.txt" が存在しない場合、プログラムはエラーで終了せず、フレンドリーなメッセージを表示します。これでユーザーの「やらかし」への耐性が上がります。
例 2: アクセス権の問題
もう少し複雑な状況を見てみましょう: ファイルはあるけれど読み取り権限がない(例えば誰かが書き込みだけ許可した、あるいはファイルが保護されたフォルダにある)場合です。
try
{
string secret = File.ReadAllText("C:\\Windows\\System32\\config.txt");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("ファイルにアクセスできません! 管理者としてプログラムを実行してみてください。");
Console.WriteLine("技術的な理由: " + ex.Message);
}
例 3: 一般的な IOException
いくつかのエラーはロックの競合(別プロセスがファイルを開いている等)、ディスク容量不足、ハードウェアの問題などに関連します:
try
{
File.WriteAllText("important.txt", "重要な情報!");
}
catch (IOException ex)
{
Console.WriteLine("ファイル操作中のエラー: おそらく別のプログラムがファイルを使用しているか、ディスクの空きが足りません。");
Console.WriteLine("技術的な理由: " + ex.Message);
}
4. 複数の例外をキャッチする: 選択的な処理
異なる種類の例外に対して異なる対応をしたいことがあります。.NET では複数の catch ブロックを指定でき、より具体的なものを先に、一般的なものを後に書く必要があります(そうしないとコンパイラが怒ります)。
try
{
string content = File.ReadAllText("file.txt");
Console.WriteLine(content);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("ファイルへのアクセスが拒否されました!");
}
catch (IOException ex)
{
Console.WriteLine("その他の入出力エラー: " + ex.Message);
}
重要: もし最初に catch (Exception ex) を置くと、他のブロックは意味を成さなくなります — 基底型がすべてをキャッチしてしまうからです!
5. ネストした try-catch と再試行
エラーを処理するだけでなく、ユーザーに「やり直す」チャンスを与えたい場合があります — たとえば存在するファイルのパスを入力させるなど:
string filePath;
string content;
int attempts = 0;
const int maxAttempts = 3;
do
{
Console.Write("ファイルのパスを入力してください: ");
filePath = Console.ReadLine();
try
{
content = File.ReadAllText(filePath);
Console.WriteLine("ファイルの内容:\n" + content);
break;
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません! もう一度試してください。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルにアクセスできません! 別のファイルを試してください。");
}
attempts++;
}
while (attempts < maxAttempts);
if (attempts == maxAttempts)
Console.WriteLine("試行回数が多すぎます。");
このコードはファイルの場所を忘れたユーザーに対する小さな「フレンドリーなインターフェース」です。
6. 典型的なミスと注意点: 怠慢から意識的に
初心者(そして経験者にもある)がやりがちなミスは、すべての例外を一括でキャッチして単に catch (Exception) と書き、「エラーが発生しました!」と出すだけにすることです。これはいくつかの理由で良くありません。まず第一に、アプリのビジネスロジックの本当の問題を隠してしまいます。第二に、ファイルに関係ない別のエラー(例えばコードのタイプミスや数学的なバグ)が勝手に飲み込まれてしまい、根本原因の調査が長引きます。
はるかに良いのは、自分で意味のある対処ができる例外だけを明示的にキャッチすることです。どんな例外が起きたか分からない場合は、キャッチせずに落ちる(つまり例外をそのまま発生させる)ほうが良いことがあります: そうすればスタックトレースが表示され、何を直すべきか分かります。
注意点: 一部の例外は内部に原因を持つことがあります(InnerException)。エラーログを書くときにはこれを解析すると詳細な診断に役立ちます。
もう一つのポイントは、もし catch ブロックの後でプログラムの継続が不可能な場合(例えば主要な設定ファイルを開けなかった場合)は、return で終了させるか、場合によっては例外を再スロー(throw;)して「不完全な」プログラムを残さないようにすることです。
GO TO FULL VERSION