はじめに なぜ「ファイル一覧取得」が業務の入り口になるのか
業務システムでファイルを扱う処理は、ほぼ必ず「まず対象フォルダのファイル一覧を取る」ところから始まります。
たとえば「このフォルダの CSV を全部読み込む」「ログファイルを日付順に並べて古いものから削除する」「バックアップ対象のファイルを一括コピーする」など、どれも最初の一歩は「一覧を取る」です。
だからこそ、「ファイル一覧取得」をきちんとユーティリティとして押さえておくと、その上に乗る処理(コピー・削除・圧縮・解析など)がとても書きやすくなります。
ここでは、C# 初心者向けに、Directory.GetFiles と Directory.EnumerateFiles の基本から、検索条件・再帰検索・ソート・実務ユーティリティ化まで、丁寧に解説していきます。
基本の API Directory.GetFiles の使い方
一番シンプルな「フォルダ直下のファイル一覧」
まずは、「指定フォルダ直下のファイル一覧を取得して表示する」という、一番シンプルな例から見てみましょう。
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\data";
string[] files = Directory.GetFiles(dir);
foreach (string file in files)
{
Console.WriteLine(file);
}
}
}
C#Directory.GetFiles(directoryPath) は、「そのフォルダ直下にあるすべてのファイルのフルパス」を配列で返します。
ここで重要なのは、「サブフォルダの中までは見に行かない」という点です。
「直下だけでいいのか」「配下全部を対象にしたいのか」は、業務要件によって変わるので、ここを意識しておくことが大事です。
検索パターンで絞り込む(例:CSV だけ)
「全部のファイル」ではなく、「特定のパターンに合うファイルだけ」を一覧にしたいことも多いです。
その場合は、第二引数に検索パターン(ワイルドカード)を指定します。
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\data";
string[] csvFiles = Directory.GetFiles(dir, "*.csv");
foreach (string file in csvFiles)
{
Console.WriteLine(file);
}
}
}
C#"*.csv" は「拡張子が .csv のファイルすべて」という意味です。"report_*.txt" のように、「ファイル名の一部+拡張子」で絞り込むこともできます。
ここで押さえておきたいのは、「拡張子検索もファイル名パターン検索も、同じ searchPattern でできる」ということです。
「拡張子だけで絞る」「ファイル名のプレフィックスで絞る」など、業務ルールに合わせて柔軟に指定できます。
サブフォルダも含めて一覧を取る SearchOption
業務では、「指定フォルダ配下のすべてのファイルを対象にしたい」ことがよくあります。
その場合は、第三引数に SearchOption.AllDirectories を指定します。
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\logs";
string[] allFiles = Directory.GetFiles(
dir,
"*.*",
SearchOption.AllDirectories);
foreach (string file in allFiles)
{
Console.WriteLine(file);
}
}
}
C#"*.*" は「すべてのファイル」という意味です(実際には拡張子なしもあるので、完全ではありませんが、実務上はほぼこれで足ります)。SearchOption.AllDirectories によって、「サブフォルダを再帰的にたどって、配下のファイルを全部列挙する」動きになります。
「直下だけでよい」場合は SearchOption.TopDirectoryOnly(省略時のデフォルト)です。
この二つを切り替えられるようにしておくと、ユーティリティとして使い回しやすくなります。
実務で使える「ファイル一覧取得」ユーティリティ
単純な一覧取得ユーティリティ(直下/再帰切り替え)
まずは、「指定フォルダのファイル一覧を取得する」基本ユーティリティを作ってみます。
using System;
using System.IO;
public static class FileListUtil
{
public static string[] GetFileList(
string directoryPath,
bool includeSubDirectories = false)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"ディレクトリが見つかりません: {directoryPath}");
}
SearchOption option = includeSubDirectories
? SearchOption.AllDirectories
: SearchOption.TopDirectoryOnly;
return Directory.GetFiles(directoryPath, "*.*", option);
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\data";
string[] files = FileListUtil.GetFileList(
dir,
includeSubDirectories: true);
Console.WriteLine("ファイル一覧:");
foreach (string file in files)
{
Console.WriteLine(file);
}
}
}
C#ここでの重要ポイントは、「存在チェックをユーティリティ側でやっている」ことです。
存在しないパスを渡されたときに、呼び出し側で毎回 Directory.Exists を書くのは面倒なので、ユーティリティの中で例外として扱うかどうかを決めておくと、コードがすっきりします。
検索パターン付きの一覧取得ユーティリティ
次に、「ファイル名パターンで絞り込める」バージョンを作ります。
public static class FileListUtil
{
public static string[] GetFileList(
string directoryPath,
string searchPattern,
bool includeSubDirectories = false)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"ディレクトリが見つかりません: {directoryPath}");
}
if (string.IsNullOrWhiteSpace(searchPattern))
{
searchPattern = "*.*";
}
SearchOption option = includeSubDirectories
? SearchOption.AllDirectories
: SearchOption.TopDirectoryOnly;
return Directory.GetFiles(directoryPath, searchPattern, option);
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\logs";
string[] logFiles = FileListUtil.GetFileList(
dir,
"*.log",
includeSubDirectories: true);
Console.WriteLine("ログファイル一覧:");
foreach (string file in logFiles)
{
Console.WriteLine(file);
}
}
}
C#このように、「パターン付き」「再帰あり/なし」を一つのユーティリティで表現できると、業務ロジック側はかなり読みやすくなります。
ファイル一覧を「並べ替える」ユーティリティ
更新日時順に並べる(新しい順・古い順)
一覧を取ったあと、「新しい順に処理したい」「古い順から削除したい」といった要件がよくあります。
その場合は、FileInfo と LINQ を使ってソートします。
using System;
using System.IO;
using System.Linq;
public static class FileListSortUtil
{
public static FileInfo[] GetFilesSortedByLastWriteTime(
string directoryPath,
string searchPattern = "*.*",
bool includeSubDirectories = false,
bool descending = false)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"ディレクトリが見つかりません: {directoryPath}");
}
SearchOption option = includeSubDirectories
? SearchOption.AllDirectories
: SearchOption.TopDirectoryOnly;
string[] paths = Directory.GetFiles(directoryPath, searchPattern, option);
var query = paths.Select(p => new FileInfo(p));
FileInfo[] result = descending
? query.OrderByDescending(f => f.LastWriteTime).ToArray()
: query.OrderBy(f => f.LastWriteTime).ToArray();
return result;
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\logs";
FileInfo[] files = FileListSortUtil.GetFilesSortedByLastWriteTime(
dir,
"*.log",
includeSubDirectories: false,
descending: true);
Console.WriteLine("更新日時の新しい順:");
foreach (FileInfo fi in files)
{
Console.WriteLine($"{fi.LastWriteTime:yyyy-MM-dd HH:mm:ss} {fi.FullName}");
}
}
}
C#ここでの重要ポイントは、「一覧取得とソートを分けて考える」ことです。Directory.GetFiles はあくまで「パスの配列」を返すだけなので、「どの順番で処理したいか」は自分で決める必要があります。
業務ロジックでは、「更新日時順」「ファイル名順」「サイズ順」など、目的に応じてソート条件を変えられるようにしておくと便利です。
大量ファイル向け EnumerateFiles の使い方
GetFiles と EnumerateFiles の違い
Directory.GetFiles は、「条件に合うファイルをすべて配列に読み込んでから返す」メソッドです。
ファイル数が数万・数十万と増えてくると、一度に配列に読み込むのが重くなることがあります。
その場合は、Directory.EnumerateFiles を使うと、「見つかったファイルを順次列挙しながら処理する」ことができます。
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\bigdata";
foreach (string file in Directory.EnumerateFiles(
dir,
"*.*",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}
}
}
C#EnumerateFiles は「列挙可能なシーケンス」を返し、foreach で回すときに初めて実際の検索が進んでいきます。
これにより、「全部をメモリに載せてから処理する」のではなく、「一件ずつ処理しながら進む」スタイルが取れます。
初心者向けの目安としては、「ファイル数が少ないうちは GetFiles で十分」「大量ファイルを扱うようになったら EnumerateFiles も検討する」と覚えておけば大丈夫です。
EnumerateFiles をユーティリティ化する
大量ファイルを前提にするなら、最初から IEnumerable<string> を返すユーティリティを用意しておくのも手です。
using System;
using System.IO;
using System.Collections.Generic;
public static class FileListStreamUtil
{
public static IEnumerable<string> EnumerateFileList(
string directoryPath,
string searchPattern = "*.*",
bool includeSubDirectories = false)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"ディレクトリが見つかりません: {directoryPath}");
}
SearchOption option = includeSubDirectories
? SearchOption.AllDirectories
: SearchOption.TopDirectoryOnly;
return Directory.EnumerateFiles(directoryPath, searchPattern, option);
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\bigdata";
foreach (string file in FileListStreamUtil.EnumerateFileList(
dir,
"*.csv",
includeSubDirectories: true))
{
Console.WriteLine(file);
}
}
}
C#このスタイルに慣れておくと、「見つけたそばから処理する」ストリーミング的なバッチを書きやすくなります。
例外とエラー処理を意識したファイル一覧取得
どんな例外が起こり得るか
ファイル一覧取得では、次のような理由で例外が発生する可能性があります。
ディレクトリが存在しない。
権限がなくてフォルダの中身を列挙できない。
ネットワークドライブの接続が切れている。
パスが長すぎる、または不正な文字が含まれている。
これらはすべて、「一覧を取得できなかった理由」です。
実務では、「どのフォルダで、どんな理由で失敗したか」をログに残しておくと、後から調査しやすくなります。
呼び出し側での例外処理の例
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\data";
try
{
string[] files = FileListUtil.GetFileList(
dir,
"*.csv",
includeSubDirectories: true);
Console.WriteLine("ファイル一覧:");
foreach (string file in files)
{
Console.WriteLine(file);
}
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("ディレクトリが見つかりません: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("権限エラーが発生しました: " + ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("入出力エラーが発生しました: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("想定外のエラーが発生しました: " + ex.Message);
}
}
}
C#本番環境では、これらのメッセージをログに出力することで、「定期バッチがどこまで成功しているか」「どこで詰まっているか」を可視化できます。
まとめ 実務で使える「ファイル一覧取得」ユーティリティの考え方
ファイル一覧取得は、「どのファイルを対象にするか」を決める入り口であり、その後ろに続くコピー・削除・解析・圧縮など、あらゆる処理の土台になります。
だからこそ、「その場しのぎで GetFiles を直書きする」のではなく、ユーティリティとして整理しておく価値があります。
Directory.GetFiles を使って、「直下だけ」「パターン付き」「サブフォルダも含めて」の基本パターンを押さえること。
存在チェックや検索パターン、再帰有無を引数で切り替えられる FileListUtil のようなユーティリティを用意すること。
必要に応じて FileInfo に変換し、「更新日時順」「ファイル名順」など、業務に合った並べ替えを行うこと。
大量ファイルを扱う可能性があるなら、EnumerateFiles を使ったストリーミング的な一覧取得も視野に入れること。
例外や失敗をログに残し、「どのフォルダで何が起きたか」を後から追えるようにしておくこと。
ここまで押さえておけば、「対象ファイルの取りこぼし」「想定外のファイルを巻き込む」といったトラブルをかなり減らせます。
