C#において、ファイルが存在するかどうかを事前に確認する処理は、ランタイムエラーを回避しアプリケーションの信頼性を向上させるために極めて重要です。
プログラムが特定のファイルを読み込んだり、上書きしたりする前に、対象のパスが有効であることを検証することは開発の基本と言えます。
System.IO名前空間には、この目的を達成するための手段として、主に File.Exists 静的メソッドと FileInfo インスタンスクラスの2つが用意されています。
本記事では、これら2つの手法の具体的な使い方から、パフォーマンスや設計思想に基づいた使い分けの基準までを詳しく解説します。
開発シーンに合わせて最適な方法を選択できるよう、それぞれの特徴を深く掘り下げていきましょう。
File.Exists を使用した基本的なファイル存在チェック
最も手軽で一般的に利用されている手法が、System.IO.File.Exists メソッドを使用する方法です。
このメソッドは静的メソッドとして定義されているため、インスタンスを生成することなく、パスを引数に渡すだけで即座に結果を取得できます。
戻り値は bool 型であり、ファイルが存在し、かつ実行ユーザーに読み取り権限がある場合に true を返します。
パスが null や無効な文字列、あるいはディレクトリを指している場合には false を返す仕様となっています。
以下に、File.Exists を使用したシンプルなコード例を示します。
using System;
using System.IO;
class Program
{
static void Main()
{
// チェック対象のファイルパスを指定
string filePath = @"C:\temp\example.txt";
// File.Existsによる存在確認
if (File.Exists(filePath))
{
Console.WriteLine("ファイルが存在します。処理を継続します。");
}
else
{
// ファイルが存在しない、またはアクセス権限がない場合の処理
Console.WriteLine("ファイルが見つかりません。パスを確認してください。");
}
}
}
ファイルが見つかりません。パスを確認してください。
このように、単発のチェックにおいては File.Exists が最も簡潔で可読性の高い選択肢となります。
ただし、このメソッドは呼び出されるたびにファイルシステムの情報を参照するため、ループ内などで大量に呼び出す場合は注意が必要です。
FileInfo クラスを使用したオブジェクト指向的な存在確認
ファイルパスだけでなく、サイズ、作成日時、更新日時などの付随する情報を同時に扱う場合は、FileInfo クラスの利用が適しています。
FileInfo はインスタンス化のタイミングで対象ファイルの状態を取得し、内部にキャッシュする性質を持っています。
存在確認を行うには、インスタンスの Exists プロパティを参照します。
この手法の利点は、一度インスタンスを生成してしまえば、その後ファイルに関する複数の属性へ高速にアクセスできる点にあります。
次に、FileInfo クラスを用いた実装例を紹介します。
using System;
using System.IO;
class Program
{
static void Main()
{
// FileInfoインスタンスの生成
FileInfo fileInfo = new FileInfo(@"C:\temp\config.json");
// Existsプロパティによる存在確認
if (fileInfo.Exists)
{
// 存在する場合、サイズや作成日時も併せて取得可能
Console.WriteLine($"ファイル名: {fileInfo.Name}");
Console.WriteLine($"サイズ: {fileInfo.Length} バイト");
Console.WriteLine($"最終更新日時: {fileInfo.LastWriteTime}");
}
else
{
Console.WriteLine("指定されたファイルは存在しません。");
}
}
}
指定されたファイルは存在しません。
FileInfo.Exists を使用する場合、インスタンスを作成した後にファイルが削除されても、プロパティの値が自動的に更新されないことがある点に留意してください。
最新の状態を取得したい場合は、Refresh メソッドを呼び出して内部情報を更新する必要があります。
File.Exists と FileInfo の決定的な違い
どちらの機能を使うべきか迷った際は、その後の処理の流れを考慮することが判断のポイントとなります。
以下の表は、それぞれの主な違いをまとめたものです。
| 比較項目 | File.Exists (静的メソッド) | FileInfo.Exists (インスタンス) |
|---|---|---|
| 使用推奨シーン | 存在確認のみを1回行う場合 | 存在確認後に属性取得や操作を行う場合 |
| 記述の簡潔さ | 非常に高い | インスタンス化の手間が必要 |
| パフォーマンス | 単発実行なら高速 | 複数属性へのアクセス時に有利 |
| 情報の鮮度 | 常に最新(呼び出しの都度確認) | インスタンス生成時のキャッシュ(Refreshが必要) |
「ファイルが存在するかどうかだけを知りたい」という単純な要件であれば、File.Exists を選ぶのがモダンな C# 開発のスタンダードです。
逆に、「存在を確認した後にそのままファイルを移動させる」「サイズをチェックして条件分岐させる」といった複合的な処理を行うなら、FileInfo を活用してコードの冗長さを抑えるのが賢明です。
ファイル存在チェックにおける注意点とトラブルシューティング
実務的なアプリケーション開発においては、単に Exists を呼ぶだけでは不十分なケースが多々あります。
特に考慮すべきなのが、「競合状態 (Race Condition)」と呼ばれる問題です。
これは、Exists で存在を確認してから、実際にファイルを開こうとするわずかな時間の間に、別のプロセスによってファイルが削除・移動されてしまう現象を指します。
そのため、存在チェックを過信せず、常にファイルアクセス時には try-catch 文による例外処理を組み合わせるのが鉄則です。
try
{
string path = "data.txt";
if (File.Exists(path))
{
// 存在しても、この瞬間に削除される可能性がある
using (var stream = File.OpenRead(path))
{
// 読み取り処理
}
}
}
catch (FileNotFoundException ex)
{
// 存在チェックを潜り抜けて発生した例外への対処
Console.WriteLine($"エラー: {ex.Message}");
}
catch (UnauthorizedAccessException ex)
{
// アクセス権限がない場合の対処
Console.WriteLine($"権限エラー: {ex.Message}");
}
また、パスの指定についても注意が必要です。
Windows 環境ではパスの区切り文字として \ を使用しますが、文字列リテラル内では \\ とエスケープするか、@ を付けた逐次指定文字列を使用してください。
さらに、.NET 7 以降では、ファイルとディレクトリの両方を一括でチェックできる Path.Exists メソッドも導入されています。
対象がファイルなのかディレクトリなのかを問わず、とにかく「そのパスに何かが存在するか」を知りたい場合には、この新しい API の活用も検討しましょう。
ネットワークパスと大文字・小文字の区別
ファイルシステムの挙動は、プログラムが動作する OS やストレージの種類によっても変化します。
Windows 上で動作する場合、通常ファイルパスの大文字と小文字は区別されません。
しかし、Linux や macOS 上で .NET アプリケーションを実行する場合、File.Exists("test.txt") と File.Exists("TEST.TXT") は異なる結果を返す可能性があります。
クロスプラットフォーム展開を想定している場合は、常に正確なケース(大文字小文字)でパスを扱うように設計してください。
また、ネットワーク共有フォルダ (UNCパス) を指定する場合、ネットワークの遅延や接続断によって Exists メソッドの戻り値が返るまで時間がかかることがあります。
タイムアウトの概念が標準の File.Exists には存在しないため、高負荷な環境や不安定なネットワーク環境では、非同期処理を検討する必要があるかもしれません。
まとめ
C#でのファイル存在チェックは、単純に見えて奥が深いテーマです。
手軽に確認したい場合は File.Exists を、オブジェクト指向的に詳細な情報を扱いたい場合は FileInfo を選択しましょう。
そして、存在確認はあくまで「エラーの可能性を減らすための事前チェック」であることを忘れてはいけません。
実際のファイル操作時には、必ず例外処理を実装して、予期せぬ実行時エラーからアプリケーションを保護するようにしてください。
最新の C# および .NET 環境では、パフォーマンス向上やクロスプラットフォーム対応が進んでおり、適切なメソッド選択がより重要になっています。
今回解説した使い分けの基準を参考に、堅牢でメンテナンス性の高いコードを記述していきましょう。
