C#でアプリケーションを開発する際、特定のフォルダ内にあるファイルの一覧を取得する処理は非常に頻繁に発生します。
System.IO名前空間に用意されているDirectory.GetFilesメソッドは、そのための最も基本的かつ強力な手段の一つです。
本記事では、Directory.GetFilesの基本的な使い方から、実務で役立つ検索オプション、さらには大量のファイルを扱う際の高速化テクニックまで詳しく解説します。
2026年現在のモダンなC#開発においても、ファイル操作の最適化はアプリケーションのレスポンス性能に直結する重要な要素です。
基本を押さえつつ、パフォーマンスを意識した実装方法を身につけていきましょう。
Directory.GetFilesメソッドの基本的な使い方
Directory.GetFilesは、指定したディレクトリ内にあるファイル名のパスを文字列配列(string[])として返すメソッドです。
最もシンプルな使い方は、引数にパスのみを指定する方法です。
using System;
using System.IO;
class Program
{
static void Main()
{
// 取得したいディレクトリのパスを指定します
string targetPath = @"C:\SampleDirectory";
// ディレクトリが存在するか確認してから取得します
if (Directory.Exists(targetPath))
{
// ファイル一覧をフルパスの配列で取得します
string[] files = Directory.GetFiles(targetPath);
foreach (string file in files)
{
Console.WriteLine(file);
}
}
}
}
C:\SampleDirectory\data1.txt
C:\SampleDirectory\image.png
C:\SampleDirectory\config.json
このコードでは、指定したフォルダの直下にあるすべてのファイルが取得されます。
ただし、この単純な呼び出しではサブフォルダの中身までは含まれない点に注意が必要です。
また、対象のディレクトリが存在しない場合に呼び出すとDirectoryNotFoundExceptionが発生するため、必ずDirectory.Existsによるチェックを行うのが定石です。
検索パターン(ワイルドカード)を利用した絞り込み
特定の拡張子を持つファイルだけを取得したい場合、第2引数に「検索パターン」を指定します。
検索パターンには、アスタリスク (*) や疑問符 (?) といったワイルドカードを使用できます。
特定の拡張子を指定して取得する
例えば、テキストファイル(.txt)のみを取得したい場合は以下のように記述します。
string targetPath = @"C:\SampleDirectory";
// *.txt というパターンで絞り込みます
string[] textFiles = Directory.GetFiles(targetPath, "*.txt");
この方法は非常に簡単ですが、Windowsのファイルシステムにおける「短縮ファイル名」の挙動により、意図しないファイルがヒットすることがあります。
例えば、*.htmを指定した場合に.htmlファイルも取得されてしまう可能性があることは覚えておきましょう。
複雑な名前パターンの指定
「data」から始まるファイルのみを取得したい場合は"data*"と記述します。
また、1文字だけを任意とする場合は"test?.dat"のように疑問符を使用します。
これらを組み合わせることで、プログラム側での複雑なフィルタリング処理を書かずに、OSレベルに近い部分で効率的にファイルを抽出できます。
検索オプションによるサブディレクトリの探索
デフォルトでは直下のファイルしか取得しませんが、第3引数にSearchOptionを指定することで、サブディレクトリを含めた再帰的な検索が可能になります。
| 列挙値 | 説明 |
|---|---|
| TopDirectoryOnly | 指定したディレクトリの直下のみを検索します(既定値)。 |
| AllDirectories | サブディレクトリを含め、すべての階層を再帰的に検索します。 |
// 全てのサブディレクトリを含めて検索する例
string[] allFiles = Directory.GetFiles(targetPath, "*.*", SearchOption.AllDirectories);
SearchOption.AllDirectoriesは非常に便利ですが、注意点もあります。
検索対象の中にアクセス権限がないフォルダが含まれている場合、UnauthorizedAccessExceptionが発生して処理全体が中断されるという問題です。
深い階層やシステムフォルダを検索する場合は、この後解説する例外対策や別の手段を検討する必要があります。
GetFilesとEnumerateFilesの違いと使い分け
C#には、ファイル一覧を取得するもう一つの強力なメソッドとしてDirectory.EnumerateFilesが存在します。
これら2つのメソッドの最大の違いは、「すべての結果を一度にメモリへ読み込むか、一つずつ順次処理するか」にあります。
Directory.GetFiles の特徴
GetFilesは、すべてのファイル名を取得し終わるまで呼び出し元に制御を戻しません。
戻り値は配列(string[])であり、取得したデータはすべてメモリ上に保持されます。
ファイル数が数百から数千程度であれば問題ありませんが、数万件を超えるとメモリ消費量が増大し、処理開始までの待ち時間が発生します。
Directory.EnumerateFiles の特徴
一方、EnumerateFilesは戻り値がIEnumerable<string>となっており、遅延実行(Lazy Evaluation)が行われます。
ファイルが見つかるたびに一つずつ結果を返すため、ループ処理と組み合わせることで即座に処理を開始できます。
// EnumerateFilesを使用すると、全ての取得を待たずにループが始まります
var fileEnum = Directory.EnumerateFiles(@"C:\LargeDirectory", "*.*", SearchOption.AllDirectories);
foreach (var file in fileEnum)
{
// 見つかったものから順番に処理できるため、初期応答が速い
Console.WriteLine(file);
}
大規模なファイルシステムを対象にする場合、メモリ消費を抑えつつ高速に動作するEnumerateFilesの利用を推奨します。
LINQを組み合わせた高度なフィルタリング
Directory.GetFilesやEnumerateFilesで取得した結果に対し、LINQ(Language Integrated Query)を使用することで柔軟な条件指定が可能になります。
複数の拡張子を対象にする
標準の検索パターンでは「.jpg または .png」といった複数の条件を一度に指定できません。
このようなケースでは、LINQのWhere句を活用します。
using System.Linq;
string[] extensions = { ".jpg", ".png", ".gif" };
var imageFiles = Directory.EnumerateFiles(targetPath)
.Where(file => extensions.Contains(Path.GetExtension(file).ToLower()));
このコードは非常に読みやすく、拡張子の追加や変更も容易です。
ファイルサイズや更新日時で絞り込む
ファイル名だけでなく、属性情報を条件にしたい場合はFileInfoクラスを併用するのが効率的です。
// 10MB以上のファイルだけを抽出する例
var largeFiles = new DirectoryInfo(targetPath)
.EnumerateFiles("*.*", SearchOption.AllDirectories)
.Where(f => f.Length > 10 * 1024 * 1024)
.Select(f => f.FullName);
FileInfoオブジェクトを列挙することで、サイズ(Length)や最終更新日時(LastWriteTime)に基づいた高度なフィルタリングが実現できます。
ファイル取得時の例外処理とエラー対策
実務的なアプリケーションにおいて、ファイル操作は常にエラーの危険と隣り合わせです。
特にSearchOption.AllDirectoriesを使用する際、アクセス権のないシステムフォルダに触れるとプログラムがクラッシュしてしまいます。
アクセス権限エラーへの対応
Directory.GetFilesでは一つのエラーで処理が止まってしまいますが、より堅牢な実装をするには、再帰処理を自作するか、例外をスキップするオプション(.NET Core 2.1以降)を使用します。
EnumerationOptionsクラスを使用することで、アクセス禁止フォルダを無視する設定が可能です。
var options = new EnumerationOptions
{
// アクセスできないディレクトリがあっても無視して続行する
IgnoreInaccessible = true,
// サブディレクトリも検索する
RecurseSubdirectories = true
};
var files = Directory.EnumerateFiles(targetPath, "*.*", options);
このEnumerationOptionsの活用は、現代のC#におけるファイル検索のベストプラクティスと言えます。
これにより、try-catchを複雑にネストさせることなく、安全に全検索を行うことができます。
高速化のためのポイント:パス操作とパフォーマンス
大量のファイルを処理する場合、文字列としてのパス操作自体がオーバーヘッドになることがあります。
Path.CombineやPath.GetExtensionなどのメソッドは便利ですが、ループ内で数百万回呼び出されると無視できないコストになります。
パフォーマンスを極限まで追求する場合は、以下のポイントを意識してください。
- 可能な限り EnumerationFiles を使用し、配列の確保を避ける。
- 文字列の連結ではなく、
ReadOnlySpan<char>を活用したパス解析を検討する。 - ネットワークドライブ上のファイルをスキャンする場合、並列処理(Parallel.ForEachなど)を検討するが、ディスクI/Oのボトルネックに注意する。
また、ファイルの存在確認と取得を別々に行うのではなく、一度の列挙で必要な情報をすべて取得するように設計することも重要です。
例えば、ファイル名とサイズの両方が必要な場合、GetFilesを呼んだ後に各ファイルでnew FileInfo()を作成するのは非効率です。
最初からDirectoryInfo.EnumerateFilesを使い、オブジェクトをそのまま利用するようにしましょう。
まとめ
C#のDirectory.GetFilesは、非常にシンプルにファイル一覧を取得できる便利なメソッドです。
しかし、小規模なツール開発から大規模なエンタープライズシステムまで対応するためには、状況に応じた使い分けが欠かせません。
基本的にはDirectory.GetFilesで事足りますが、メモリ効率を重視するならEnumerateFiles、堅牢性を重視するならEnumerationOptionsの併用を選択しましょう。
また、LINQを組み合わせることで、フィルタリングロジックを簡潔かつ保守性の高いものに保つことができます。
今回解説したテクニックを駆使して、高速でエラーに強いファイル操作処理を実装してください。
正しいメソッド選択と適切な例外処理こそが、プロフェッショナルなC#プログラミングの第一歩です。
