C# Directory.EnumerateFilesの使い方|GetFilesとの違い・再帰検索・サンプルコードを解説

はじめに

C#でフォルダ内のファイル一覧を取得したいときによく使うのが、Directory.EnumerateFilesです。たとえば、特定フォルダ内の.txtファイルを順番に処理したり、サブフォルダを含めてログファイルを検索したり、画像ファイルだけを抽出したりする場面で役立ちます。

Directory.EnumerateFilesは、指定したディレクトリ内のファイル名を列挙可能なコレクションとして返すメソッドです。戻り値はIEnumerable<string>で、ファイルを一件ずつ処理しやすい点が特徴です。Microsoft Learnでも、Directory.EnumerateFilesは指定パス内の完全なファイル名の列挙可能なコレクションを返すメソッドとして説明されています。Microsoft Learn

C#でファイル一覧を取得する方法には、Directory.GetFilesもあります。どちらもファイル検索に使えますが、EnumerateFilesは結果を順次取得でき、GetFilesは配列として一括取得するという違いがあります。大量ファイルを扱う場合は、この違いを理解しておくことが重要です。

この記事では、C#のDirectory.EnumerateFilesの基本的な使い方、GetFilesとの違い、再帰検索、検索パターン、EnumerationOptions、よくあるエラーと対処法まで、サンプルコード付きで解説します。

1. C#のDirectory.EnumerateFilesとは

1-1. Directory.EnumerateFilesの基本的な役割

Directory.EnumerateFilesは、C#で指定したフォルダ内のファイル一覧を取得するためのメソッドです。System.IO.Directoryクラスに用意されており、ファイルパスの一覧をIEnumerable<string>として返します。

基本的な役割は、次のとおりです。

役割内容
ファイル一覧の取得指定フォルダ内のファイルパスを取得する
条件付き検索*.txtlog_*などのパターンで絞り込む
再帰検索サブフォルダを含めて検索する
順次処理ファイルを1件ずつ処理する

たとえば、次のように書くと、指定したフォルダ内のファイルを順番に表示できます。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
foreach (string file in Directory.EnumerateFiles(@"C:\Work"))
{
Console.WriteLine(file);
}
}
}

Directory.EnumerateFilesで取得される値は、通常、ファイル名だけではなくパスを含んだ文字列です。ファイル名だけが必要な場合は、後述するPath.GetFileNameを組み合わせます。

1-2. 指定フォルダ内のファイル一覧を取得できるメソッド

C#でファイル一覧を取得する代表的なメソッドには、次のようなものがあります。

メソッド戻り値主な用途
Directory.EnumerateFilesIEnumerable<string>ファイルパスを順次取得する
Directory.GetFilesstring[]ファイルパスを配列で一括取得する
DirectoryInfo.EnumerateFilesIEnumerable<FileInfo>ファイル情報を含めて順次取得する
Directory.EnumerateDirectoriesIEnumerable<string>フォルダ一覧を取得する
Directory.EnumerateFileSystemEntriesIEnumerable<string>ファイルとフォルダの両方を取得する

ファイルのパス文字列だけで十分な場合は、Directory.EnumerateFilesがシンプルです。一方、ファイルサイズ、更新日時、属性などを頻繁に使う場合は、DirectoryInfo.EnumerateFilesFileInfoを取得する方法もあります。Microsoft Learnでも、ファイル名を取得する場合はDirectory.EnumerateFiles、ファイル情報を取得する場合はDirectoryInfo.EnumerateFilesを使う形で整理されています。Microsoft Learn

1-3. System.IO名前空間を使う準備

Directory.EnumerateFilesを使うには、System.IO名前空間を利用します。

C#
using System.IO;

完全修飾名で書くこともできます。

C#
var files = System.IO.Directory.EnumerateFiles(@"C:\Work");

ただし、通常はファイル操作を行うコードの先頭にusing System.IO;を書いておくと、DirectoryFilePathFileInfoなどを簡潔に扱えます。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
var files = Directory.EnumerateFiles(@"C:\Work");

foreach (var file in files)
{
Console.WriteLine(Path.GetFileName(file));
}
}
}

1-4. Directory.EnumerateFilesを使う場面

Directory.EnumerateFilesは、次のような場面でよく使われます。

使用場面
ファイル一覧を画面に表示するフォルダ内のファイル名をコンソールに出力する
特定拡張子のファイルを検索する.txt.csv.jpgだけを取得する
ログファイルを処理する*.logを検索して内容を読み込む
バックアップ対象を探す更新日時が新しいファイルだけを抽出する
大量ファイルを順番に処理するメモリ使用量を抑えながらファイルを処理する
サブフォルダも含めて検索するSearchOption.AllDirectoriesで再帰検索する

特に、大量のファイルを扱う処理では、Directory.GetFilesよりもDirectory.EnumerateFilesのほうが適しているケースがあります。EnumerateFilesは全件取得が終わる前に列挙を開始できるため、ファイルを1件ずつ読み込みながら処理できます。Microsoft Learn

2. Directory.EnumerateFilesの基本的な使い方

2-1. 最小構成のサンプルコード

Directory.EnumerateFilesの最小構成は、検索対象のフォルダパスを指定するだけです。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
var files = Directory.EnumerateFiles(@"C:\Work");

foreach (string file in files)
{
Console.WriteLine(file);
}
}
}

このコードでは、C:\Workフォルダの直下にあるファイルを取得し、1件ずつコンソールに表示します。

サブフォルダの中のファイルは、この書き方では取得されません。サブフォルダを含めて検索したい場合は、後述するSearchOption.AllDirectoriesを指定します。

2-2. 取得したファイルパスをforeachで処理する方法

Directory.EnumerateFilesの戻り値はIEnumerable<string>です。そのため、foreachで順番に処理できます。

C#
foreach (string filePath in Directory.EnumerateFiles(@"C:\Work"))
{
Console.WriteLine(filePath);
}

ファイル名だけを表示したい場合は、Path.GetFileNameを使います。

C#
foreach (string filePath in Directory.EnumerateFiles(@"C:\Work"))
{
string fileName = Path.GetFileName(filePath);
Console.WriteLine(fileName);
}

拡張子だけを取得する場合は、Path.GetExtensionを使います。

C#
foreach (string filePath in Directory.EnumerateFiles(@"C:\Work"))
{
string extension = Path.GetExtension(filePath);
Console.WriteLine(extension);
}

ファイルごとに読み込み処理を行う場合は、次のように書けます。

C#
foreach (string filePath in Directory.EnumerateFiles(@"C:\Logs", "*.log"))
{
string text = File.ReadAllText(filePath);
Console.WriteLine(text);
}

ただし、大きなファイルを扱う場合は、File.ReadAllTextではなくFile.ReadLinesを使って1行ずつ処理するほうがメモリ効率がよくなります。

2-3. 絶対パスと相対パスを指定する違い

Directory.EnumerateFilespathには、絶対パスと相対パスのどちらも指定できます。公式ドキュメントでも、pathは検索するディレクトリへの相対パスまたは絶対パスとして説明されています。Microsoft Learn

絶対パスの例です。

C#
var files = Directory.EnumerateFiles(@"C:\Work\Reports");

相対パスの例です。

C#
var files = Directory.EnumerateFiles(@"Reports");

相対パスは、現在の作業ディレクトリを基準に解釈されます。現在の作業ディレクトリは、Directory.GetCurrentDirectory()で確認できます。

C#
Console.WriteLine(Directory.GetCurrentDirectory());

foreach (string file in Directory.EnumerateFiles(@"Reports"))
{
Console.WriteLine(file);
}

コンソールアプリ、Windowsアプリ、ASP.NETアプリ、テストプロジェクトでは、現在の作業ディレクトリが想定と異なることがあります。確実に対象フォルダを指定したい場合は、絶対パスを使うか、アプリケーションの基準パスから組み立てると安全です。

C#
string baseDir = AppContext.BaseDirectory;
string targetDir = Path.Combine(baseDir, "Reports");

foreach (string file in Directory.EnumerateFiles(targetDir))
{
Console.WriteLine(file);
}

2-4. 存在しないフォルダを指定した場合の注意点

存在しないフォルダを指定すると、DirectoryNotFoundExceptionが発生します。公式ドキュメントでも、指定したpathが存在しない、または無効な場合はDirectoryNotFoundExceptionが発生する例外として示されています。Microsoft Learn

C#
try
{
foreach (string file in Directory.EnumerateFiles(@"C:\NoSuchFolder"))
{
Console.WriteLine(file);
}
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("フォルダが見つかりません。");
Console.WriteLine(ex.Message);
}

事前に存在チェックを行う場合は、Directory.Existsを使います。

C#
string dir = @"C:\NoSuchFolder";

if (Directory.Exists(dir))
{
foreach (string file in Directory.EnumerateFiles(dir))
{
Console.WriteLine(file);
}
}
else
{
Console.WriteLine("指定フォルダが存在しません。");
}

実務では、ユーザーが入力したパス、設定ファイルから読み込んだパス、外部ストレージやネットワークドライブのパスを扱うときに、存在チェックや例外処理を入れておくと安心です。

3. Directory.EnumerateFilesの引数と指定方法

3-1. pathで検索対象フォルダを指定する

pathは、検索対象となるフォルダを指定する引数です。

C#
Directory.EnumerateFiles(path);

例です。

C#
string path = @"C:\Work";

foreach (string file in Directory.EnumerateFiles(path))
{
Console.WriteLine(file);
}

pathには、ファイルではなくフォルダを指定します。ファイルパスを指定した場合は、IOExceptionが発生する可能性があります。

C#
string path = @"C:\Work\sample.txt"; // フォルダではなくファイル

foreach (string file in Directory.EnumerateFiles(path))
{
Console.WriteLine(file);
}

パスを組み立てるときは、文字列連結ではなくPath.Combineを使うと、区切り文字のミスを減らせます。

C#
string baseDir = @"C:\Work";
string subDir = "Reports";
string path = Path.Combine(baseDir, subDir);

foreach (string file in Directory.EnumerateFiles(path))
{
Console.WriteLine(file);
}

3-2. searchPatternで拡張子やファイル名を絞り込む

searchPatternを指定すると、ファイル名や拡張子で検索結果を絞り込めます。

C#
Directory.EnumerateFiles(path, searchPattern);

.txtファイルだけを取得する例です。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*.txt"))
{
Console.WriteLine(file);
}

searchPatternでは、主に次のワイルドカードを使います。

ワイルドカード意味
*0文字以上の任意の文字列*.txt
?任意の1文字file?.txt

使用例です。

C#
// すべてのファイル
Directory.EnumerateFiles(@"C:\Work", "*");

// txtファイル
Directory.EnumerateFiles(@"C:\Work", "*.txt");

// fileで始まるファイル
Directory.EnumerateFiles(@"C:\Work", "file*");

// 末尾が_backup.txtのファイル
Directory.EnumerateFiles(@"C:\Work", "*_backup.txt");

// file1.txt、file2.txtなど
Directory.EnumerateFiles(@"C:\Work", "file?.txt");

searchPatternは正規表現ではありません。たとえば、[0-9]^$のような正規表現の記法は、そのまま正規表現としては扱われません。公式ドキュメントでも、searchPattern*?を含められる一方、正規表現はサポートしないと説明されています。Microsoft Learn

3-3. SearchOptionで検索範囲を指定する

SearchOptionを指定すると、検索範囲を現在のフォルダだけにするか、サブフォルダも含めるかを選べます。

C#
Directory.EnumerateFiles(path, searchPattern, searchOption);

SearchOptionには主に次の2つがあります。

意味
SearchOption.TopDirectoryOnly指定フォルダ直下のみ検索する
SearchOption.AllDirectoriesサブフォルダも含めて検索する

指定フォルダ直下だけを検索する例です。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Work",
"*.txt",
SearchOption.TopDirectoryOnly))
{
Console.WriteLine(file);
}

サブフォルダも含めて検索する例です。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Work",
"*.txt",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}

SearchOption.AllDirectoriesは便利ですが、フォルダ階層が深い場合やファイル数が多い場合は処理に時間がかかります。また、シンボリックリンクやジャンクションなどでループする構造がある場合は注意が必要です。Microsoft Learnでは、AllDirectoriesを選んだ検索でループを作るリンクが含まれると、検索処理が無限ループに入る可能性があると説明されています。Microsoft Learn

3-4. EnumerationOptionsを使った詳細な検索設定

EnumerationOptionsを使うと、再帰検索、アクセス不可ファイルの扱い、大文字小文字の一致、属性の無視などをより細かく設定できます。

C#
var options = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true
};

foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*.txt", options))
{
Console.WriteLine(file);
}

よく使うプロパティには、次のようなものがあります。

プロパティ内容
RecurseSubdirectoriesサブフォルダを再帰的に検索する
IgnoreInaccessibleアクセスできないファイルやフォルダをスキップする
MatchCasing大文字小文字の一致方法を指定する
AttributesToSkipスキップする属性を指定する
ReturnSpecialDirectories...を返すかどうかを指定する

アクセス権限がないフォルダをスキップしたい場合は、IgnoreInaccessibleが便利です。このプロパティは、アクセスが拒否されたファイルやディレクトリをスキップするかどうかを設定するものです。Microsoft Learn

C#
var options = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true
};

foreach (string file in Directory.EnumerateFiles(@"C:\", "*.log", options))
{
Console.WriteLine(file);
}

SearchOption.AllDirectoriesよりも柔軟な設定をしたい場合は、EnumerationOptionsを使うとよいでしょう。

4. Directory.EnumerateFilesで特定のファイルを検索する方法

4-1. すべてのファイルを取得するサンプル

すべてのファイルを取得する場合は、searchPatternを省略するか、"*"を指定します。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work"))
{
Console.WriteLine(file);
}

"*"を明示的に指定することもできます。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*"))
{
Console.WriteLine(file);
}

どちらも、指定フォルダ直下のファイルを取得します。

4-2. txtファイルだけを取得するサンプル

.txtファイルだけを取得するには、"*.txt"を指定します。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*.txt"))
{
Console.WriteLine(file);
}

ファイル名だけを表示したい場合は、Path.GetFileNameを使います。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*.txt"))
{
Console.WriteLine(Path.GetFileName(file));
}

サブフォルダも含めて.txtファイルを探す場合は、SearchOption.AllDirectoriesを指定します。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Work",
"*.txt",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}

4-3. ファイル名の一部に一致するファイルを検索する方法

ファイル名の一部に一致するファイルを探す場合は、*を使います。

C#
// reportを含むtxtファイル
foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*report*.txt"))
{
Console.WriteLine(file);
}

先頭がlog_のファイルを探す例です。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Logs", "log_*"))
{
Console.WriteLine(file);
}

末尾が_backup.csvのファイルを探す例です。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Data", "*_backup.csv"))
{
Console.WriteLine(file);
}

より複雑な条件を使いたい場合は、searchPatternだけで無理に表現せず、LINQやPathクラスと組み合わせると読みやすくなります。

C#
var files = Directory.EnumerateFiles(@"C:\Work")
.Where(file => Path.GetFileName(file).Contains("report", StringComparison.OrdinalIgnoreCase));

foreach (string file in files)
{
Console.WriteLine(file);
}

4-4. 複数拡張子を検索する方法

Directory.EnumerateFilessearchPatternには、"*.txt;*.csv"のように複数パターンを直接指定することはできません。複数拡張子を検索したい場合は、LINQで絞り込む方法がよく使われます。

C#
var extensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".txt",
".csv",
".log"
};

var files = Directory.EnumerateFiles(@"C:\Work")
.Where(file => extensions.Contains(Path.GetExtension(file)));

foreach (string file in files)
{
Console.WriteLine(file);
}

サブフォルダも含めて検索する場合は、次のように書けます。

C#
var extensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".gif"
};

var imageFiles = Directory.EnumerateFiles(
@"C:\Images",
"*",
SearchOption.AllDirectories)
.Where(file => extensions.Contains(Path.GetExtension(file)));

foreach (string file in imageFiles)
{
Console.WriteLine(file);
}

検索パターンを複数回呼び出して結合する方法もあります。

C#
var txtFiles = Directory.EnumerateFiles(@"C:\Work", "*.txt");
var csvFiles = Directory.EnumerateFiles(@"C:\Work", "*.csv");

var files = txtFiles.Concat(csvFiles);

foreach (string file in files)
{
Console.WriteLine(file);
}

ただし、拡張子が増える場合は、すべてのファイルを一度列挙してWhereで絞り込むほうが管理しやすいことがあります。

4-5. LINQと組み合わせて条件を絞り込む方法

Directory.EnumerateFilesIEnumerable<string>を返すため、LINQと相性がよいです。

更新日時が新しいファイルだけを取得する例です。

C#
DateTime border = DateTime.Now.AddDays(-7);

var files = Directory.EnumerateFiles(@"C:\Work")
.Where(file => File.GetLastWriteTime(file) >= border);

foreach (string file in files)
{
Console.WriteLine(file);
}

ファイルサイズが1MB以上のファイルだけを取得する例です。

C#
var files = Directory.EnumerateFiles(@"C:\Work")
.Where(file => new FileInfo(file).Length >= 1024 * 1024);

foreach (string file in files)
{
Console.WriteLine(file);
}

ファイル名にerrorを含むログファイルだけを取得する例です。

C#
var files = Directory.EnumerateFiles(@"C:\Logs", "*.log")
.Where(file => Path.GetFileName(file)
.Contains("error", StringComparison.OrdinalIgnoreCase));

foreach (string file in files)
{
Console.WriteLine(file);
}

並び替えたい場合は、OrderByを使います。

C#
var files = Directory.EnumerateFiles(@"C:\Work", "*.txt")
.OrderBy(file => Path.GetFileName(file));

foreach (string file in files)
{
Console.WriteLine(file);
}

5. Directory.EnumerateFilesで再帰検索する方法

5-1. サブフォルダを含めてファイルを取得する基本コード

サブフォルダを含めてファイルを取得するには、SearchOption.AllDirectoriesを指定します。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Work",
"*",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}

.txtファイルだけを再帰検索する場合は、次のように書きます。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Work",
"*.txt",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}

このコードでは、C:\Work直下だけでなく、その下にあるすべてのサブフォルダも検索対象になります。

5-2. SearchOption.AllDirectoriesの使い方

SearchOption.AllDirectoriesは、指定フォルダ以下のすべてのサブフォルダを検索対象にします。

C#
var files = Directory.EnumerateFiles(
@"C:\Project",
"*.cs",
SearchOption.AllDirectories);

foreach (string file in files)
{
Console.WriteLine(file);
}

C#のソースコードファイルだけをプロジェクト全体から探す場合などに便利です。

C#
var csFiles = Directory.EnumerateFiles(
@"C:\Projects\SampleApp",
"*.cs",
SearchOption.AllDirectories);

foreach (string file in csFiles)
{
Console.WriteLine(Path.GetFileName(file));
}

ただし、すべてのサブフォルダを無条件にたどるため、検索対象が広すぎると時間がかかります。binobj.gitなど、不要なフォルダがある場合は、除外処理を入れると効率的です。

5-3. 特定フォルダを除外して検索する方法

SearchOption.AllDirectoriesだけでは、特定フォルダを除外する細かい制御はしにくいです。除外したいフォルダがある場合は、自分でディレクトリをたどる方法が使えます。

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class Program
{
static void Main()
{
foreach (string file in EnumerateFilesExcludeDirs(@"C:\Project", "*.cs"))
{
Console.WriteLine(file);
}
}

static IEnumerable<string> EnumerateFilesExcludeDirs(string root, string pattern)
{
var excludeDirNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"bin",
"obj",
".git"
};

foreach (string file in Directory.EnumerateFiles(root, pattern))
{
yield return file;
}

foreach (string dir in Directory.EnumerateDirectories(root))
{
string dirName = Path.GetFileName(dir);

if (excludeDirNames.Contains(dirName))
{
continue;
}

foreach (string file in EnumerateFilesExcludeDirs(dir, pattern))
{
yield return file;
}
}
}
}

このコードでは、binobj.gitフォルダを除外しながら、.csファイルを再帰的に取得しています。

より安全にするなら、アクセス権限エラーを考慮して例外処理を追加します。

C#
static IEnumerable<string> EnumerateFilesSafe(string root, string pattern)
{
IEnumerable<string> files = Enumerable.Empty<string>();

try
{
files = Directory.EnumerateFiles(root, pattern);
}
catch (UnauthorizedAccessException)
{
yield break;
}
catch (DirectoryNotFoundException)
{
yield break;
}

foreach (string file in files)
{
yield return file;
}

IEnumerable<string> dirs = Enumerable.Empty<string>();

try
{
dirs = Directory.EnumerateDirectories(root);
}
catch (UnauthorizedAccessException)
{
yield break;
}
catch (DirectoryNotFoundException)
{
yield break;
}

foreach (string dir in dirs)
{
foreach (string file in EnumerateFilesSafe(dir, pattern))
{
yield return file;
}
}
}

5-4. アクセス権限がないフォルダでエラーになる場合の対処法

再帰検索では、アクセス権限がないフォルダに到達すると、UnauthorizedAccessExceptionが発生することがあります。特に、C:\直下やユーザープロファイル、システムフォルダを検索する場合は注意が必要です。

EnumerationOptionsを使える環境では、IgnoreInaccessible = trueを指定すると、アクセスできないファイルやフォルダをスキップできます。IgnoreInaccessibleは、アクセス拒否時にファイルやディレクトリをスキップするかどうかを設定するプロパティです。Microsoft Learn

C#
var options = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true
};

foreach (string file in Directory.EnumerateFiles(@"C:\", "*.log", options))
{
Console.WriteLine(file);
}

SearchOption.AllDirectoriesを使っていて例外が出る場合は、次のようにtry-catchで囲みます。

C#
try
{
foreach (string file in Directory.EnumerateFiles(
@"C:\",
"*.log",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("アクセス権限がないフォルダがあります。");
Console.WriteLine(ex.Message);
}

ただし、この書き方では途中で例外が発生すると列挙全体が止まることがあります。アクセスできないフォルダをスキップしながら続行したい場合は、EnumerationOptionsを使うか、自前で再帰処理を実装する方法が向いています。

5-5. 大量ファイルを再帰検索するときの注意点

大量ファイルを再帰検索するときは、次の点に注意します。

注意点対策
検索に時間がかかる対象フォルダや拡張子を絞る
アクセス権限エラーが出るEnumerationOptions.IgnoreInaccessibleを使う
メモリ使用量が増えるToList()ToArray()を安易に使わない
シンボリックリンクでループするリンクや再解析ポイントを考慮する
ファイルが処理中に変更されるファイル単位で例外処理する

たとえば、次のようにToList()を使うと、検索結果をすべてメモリに保持します。

C#
var files = Directory.EnumerateFiles(
@"C:\Data",
"*",
SearchOption.AllDirectories).ToList();

ファイル数が少ない場合は問題ありませんが、数十万件、数百万件になる可能性がある場合は、foreachで1件ずつ処理するほうが安全です。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Data",
"*",
SearchOption.AllDirectories))
{
ProcessFile(file);
}

6. Directory.EnumerateFilesとDirectory.GetFilesの違い

6-1. EnumerateFilesは遅延実行でファイルを順次取得する

Directory.EnumerateFilesは、列挙可能なコレクションを返します。つまり、foreachなどで実際に列挙されたタイミングで、ファイルが順次取得されます。

C#
var files = Directory.EnumerateFiles(@"C:\Work", "*.txt");

foreach (string file in files)
{
Console.WriteLine(file);
}

この性質により、全ファイルの取得完了を待たずに処理を始められます。Microsoft Learnでも、EnumerateFilesはコレクション全体が返される前に名前の列挙を開始できると説明されています。Microsoft Learn

大量ファイルを1件ずつ読み込んで処理するようなケースでは、EnumerateFilesが扱いやすいです。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Logs", "*.log"))
{
foreach (string line in File.ReadLines(file))
{
if (line.Contains("ERROR"))
{
Console.WriteLine($"{file}: {line}");
}
}
}

6-2. GetFilesは配列として一括取得する

Directory.GetFilesは、検索結果をstring[]として返します。公式ドキュメントでも、GetFilesの戻り値はファイル名の配列として示されています。Microsoft Learn

C#
string[] files = Directory.GetFiles(@"C:\Work", "*.txt");

foreach (string file in files)
{
Console.WriteLine(file);
}

GetFilesは、結果が配列として必要な場合にわかりやすいです。たとえば、ファイル数をすぐに取得したい場合は簡単です。

C#
string[] files = Directory.GetFiles(@"C:\Work", "*.txt");
Console.WriteLine(files.Length);

ただし、検索結果が大量になる場合は、配列全体を作成するまで処理を始められません。

6-3. 大量ファイル処理ではEnumerateFilesが向いている理由

大量ファイル処理では、Directory.EnumerateFilesのほうが向いていることが多いです。理由は、ファイルを順次取得して処理できるためです。

たとえば、ログファイルを検索しながら1件ずつ処理する場合、EnumerateFilesなら次のように書けます。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Logs",
"*.log",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}

検索結果をすべて配列に入れる必要がないため、メモリ使用量を抑えやすくなります。Microsoft Learnでも、多くのファイルやディレクトリを操作する場合はEnumerateFilesのほうが効率的だと説明されています。Microsoft Learn

6-4. GetFilesを使ったほうがよいケース

Directory.GetFilesが不要というわけではありません。次のような場合は、GetFilesでも問題ありません。

ケース理由
ファイル数が少ない配列化しても負荷が小さい
すぐに件数が必要files.Lengthで簡単に取得できる
配列として別メソッドに渡したいstring[]が必要なAPIに渡しやすい
全件取得後にまとめて処理したい一括処理のコードが書きやすい

例です。

C#
string[] files = Directory.GetFiles(@"C:\Work", "*.csv");

if (files.Length == 0)
{
Console.WriteLine("CSVファイルはありません。");
}
else
{
Console.WriteLine($"{files.Length}件のCSVファイルがあります。");
}

少数のファイルを対象とする簡単な処理では、GetFilesのほうが直感的に書けることもあります。

6-5. EnumerateFilesとGetFilesの使い分け比較表

Directory.EnumerateFilesDirectory.GetFilesの違いをまとめると、次のようになります。

比較項目Directory.EnumerateFilesDirectory.GetFiles
戻り値IEnumerable<string>string[]
取得方法順次取得一括取得
処理開始全件取得前に開始できる全件取得後に開始する
大量ファイル向いているメモリ負荷に注意
少数ファイル問題なく使える使いやすい
LINQとの相性よい配列なのでLINQ可能
件数取得Count()が必要Lengthで取得
主な用途1件ずつ処理する全件を配列で扱う

基本的には、大量ファイルやストリーミング的な処理ではDirectory.EnumerateFiles、小規模で配列が必要な処理ではDirectory.GetFilesと考えるとよいでしょう。

7. Directory.EnumerateFilesの実践サンプルコード

7-1. フォルダ内の画像ファイルを一覧表示する

画像ファイルだけを一覧表示するサンプルです。

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class Program
{
static void Main()
{
string dir = @"C:\Images";

var imageExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".webp"
};

var imageFiles = Directory.EnumerateFiles(dir)
.Where(file => imageExtensions.Contains(Path.GetExtension(file)));

foreach (string file in imageFiles)
{
Console.WriteLine(Path.GetFileName(file));
}
}
}

サブフォルダも含める場合は、SearchOption.AllDirectoriesを指定します。

C#
var imageFiles = Directory.EnumerateFiles(
dir,
"*",
SearchOption.AllDirectories)
.Where(file => imageExtensions.Contains(Path.GetExtension(file)));

画像ビューア、アップロード対象の抽出、サムネイル生成などで使える書き方です。

7-2. ログファイルを検索して1行ずつ読み込む

ログファイルを検索して、ERRORを含む行だけを表示する例です。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string logDir = @"C:\Logs";

foreach (string file in Directory.EnumerateFiles(
logDir,
"*.log",
SearchOption.AllDirectories))
{
foreach (string line in File.ReadLines(file))
{
if (line.Contains("ERROR", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"{file}: {line}");
}
}
}
}
}

File.ReadLinesも遅延的に行を読み込むため、大きなログファイルを扱う場合に便利です。

ファイルごとに読み込みエラーを処理したい場合は、次のようにします。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Logs",
"*.log",
SearchOption.AllDirectories))
{
try
{
foreach (string line in File.ReadLines(file))
{
if (line.Contains("ERROR", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"{file}: {line}");
}
}
}
catch (IOException ex)
{
Console.WriteLine($"読み込みエラー: {file}");
Console.WriteLine(ex.Message);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"アクセスできません: {file}");
Console.WriteLine(ex.Message);
}
}

7-3. 更新日時が新しいファイルだけを取得する

更新日時が過去24時間以内のファイルだけを取得する例です。

C#
using System;
using System.IO;
using System.Linq;

class Program
{
static void Main()
{
string dir = @"C:\Work";
DateTime border = DateTime.Now.AddDays(-1);

var recentFiles = Directory.EnumerateFiles(dir)
.Where(file => File.GetLastWriteTime(file) >= border);

foreach (string file in recentFiles)
{
Console.WriteLine($"{file} / {File.GetLastWriteTime(file)}");
}
}
}

サブフォルダも含める場合です。

C#
var recentFiles = Directory.EnumerateFiles(
dir,
"*",
SearchOption.AllDirectories)
.Where(file => File.GetLastWriteTime(file) >= border);

バックアップ対象の抽出や、最近更新されたデータファイルの処理に使えます。

7-4. ファイルサイズで絞り込む

ファイルサイズが10MB以上のファイルだけを取得する例です。

C#
using System;
using System.IO;
using System.Linq;

class Program
{
static void Main()
{
string dir = @"C:\Work";
long sizeLimit = 10 * 1024 * 1024; // 10MB

var largeFiles = Directory.EnumerateFiles(dir)
.Where(file => new FileInfo(file).Length >= sizeLimit);

foreach (string file in largeFiles)
{
var info = new FileInfo(file);
Console.WriteLine($"{info.Name}: {info.Length} bytes");
}
}
}

ファイルサイズや更新日時など複数の情報を使う場合は、FileInfoに変換すると扱いやすくなります。

C#
var largeFiles = Directory.EnumerateFiles(@"C:\Work")
.Select(file => new FileInfo(file))
.Where(info => info.Length >= 10 * 1024 * 1024);

foreach (FileInfo file in largeFiles)
{
Console.WriteLine($"{file.Name}: {file.Length} bytes");
}

7-5. 検索結果をListに変換する

Directory.EnumerateFilesの結果をList<string>として扱いたい場合は、ToList()を使います。

C#
using System.Collections.Generic;
using System.IO;
using System.Linq;

List<string> files = Directory.EnumerateFiles(@"C:\Work", "*.txt")
.ToList();

件数を表示する例です。

C#
List<string> files = Directory.EnumerateFiles(@"C:\Work", "*.txt")
.ToList();

Console.WriteLine($"{files.Count}件のtxtファイルが見つかりました。");

ただし、ToList()を呼び出すと、その時点ですべての検索結果がリストに格納されます。大量ファイルを扱う場合はメモリ使用量が増えるため、必要な場合だけ使いましょう。

Listが必要になる場面には、次のようなものがあります。

場面理由
件数を何度も使うCountを簡単に取得できる
インデックスアクセスしたいfiles[0]のように扱える
UIにバインドしたい一覧表示に使いやすい
後から追加・削除したいコレクションとして編集できる

1件ずつ処理するだけなら、ToList()せずにforeachで処理するほうが効率的です。

8. Directory.EnumerateFilesでよくあるエラーと対処法

8-1. DirectoryNotFoundExceptionの原因と対処法

DirectoryNotFoundExceptionは、指定したフォルダが存在しない場合や、パスが無効な場合に発生します。Directory.EnumerateFilesの例外として、指定したpathが無効な場合にDirectoryNotFoundExceptionが発生することが示されています。Microsoft Learn

原因の例です。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\NoSuchFolder"))
{
Console.WriteLine(file);
}

対処法は、事前にDirectory.Existsで確認することです。

C#
string dir = @"C:\NoSuchFolder";

if (!Directory.Exists(dir))
{
Console.WriteLine("フォルダが存在しません。");
return;
}

foreach (string file in Directory.EnumerateFiles(dir))
{
Console.WriteLine(file);
}

設定ファイルからパスを読み込む場合は、スペルミス、余分な空白、ネットワークドライブの接続状態も確認しましょう。

8-2. UnauthorizedAccessExceptionの原因と対処法

UnauthorizedAccessExceptionは、アクセス権限がないフォルダやファイルにアクセスしようとした場合に発生します。公式ドキュメントでも、呼び出し元に必要なアクセス許可がない場合の例外として示されています。Microsoft Learn

例です。

C#
try
{
foreach (string file in Directory.EnumerateFiles(
@"C:\System Volume Information",
"*",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("アクセス権限がありません。");
}

対処法としては、次の方法があります。

対処法内容
権限のあるフォルダを指定するユーザーフォルダやアプリ用フォルダを使う
管理者権限で実行する必要に応じて権限を上げる
IgnoreInaccessibleを使うアクセス不可の項目をスキップする
フォルダ単位で例外処理する再帰処理を自前で制御する

EnumerationOptionsを使った例です。

C#
var options = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true
};

foreach (string file in Directory.EnumerateFiles(@"C:\", "*.txt", options))
{
Console.WriteLine(file);
}

8-3. PathTooLongExceptionが発生するケース

PathTooLongExceptionは、指定したパスやファイル名が長すぎる場合に発生する可能性があります。Directory.EnumerateFilesの例外として、指定したパス、ファイル名、またはその組み合わせがシステム定義の最大長を超える場合が示されています。Microsoft Learn

深いフォルダ階層を再帰検索するときに発生しやすいです。

C#
try
{
foreach (string file in Directory.EnumerateFiles(
@"C:\VeryDeepFolder",
"*",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}
}
catch (PathTooLongException ex)
{
Console.WriteLine("パスが長すぎます。");
Console.WriteLine(ex.Message);
}

対策としては、フォルダ構成を浅くする、長すぎるファイル名を避ける、対象フォルダを絞るなどが考えられます。

8-4. searchPattern指定時に意図しない結果になる原因

searchPatternを指定したときに、想定と異なる結果になることがあります。

よくある原因は次のとおりです。

原因
正規表現として書いている.*\.txt
複数パターンを直接指定している*.txt;*.csv
大文字小文字の扱いを考慮していない.JPG.jpg
.NET Framework特有の拡張子一致に注意していない*.xls.xlsxも返るケース

searchPatternは正規表現ではなく、*?を使ったワイルドカード検索です。正規表現のように複雑な条件を使いたい場合は、いったん広めに列挙してからLINQで絞り込みます。

C#
var files = Directory.EnumerateFiles(@"C:\Work")
.Where(file =>
{
string name = Path.GetFileName(file);
return name.StartsWith("report_", StringComparison.OrdinalIgnoreCase)
&& name.EndsWith(".txt", StringComparison.OrdinalIgnoreCase);
});

foreach (string file in files)
{
Console.WriteLine(file);
}

また、.NET Frameworkでは、*.xlsbook.xlsxにも一致するような拡張子一致の特殊な動作があるため、古い環境では注意が必要です。この挙動は公式ドキュメントでも、.NET Frameworkのみの注意点として説明されています。Microsoft Learn

8-5. nullや空文字を指定した場合の注意点

pathsearchPatternnullを指定すると、ArgumentNullExceptionが発生します。公式ドキュメントでも、pathまたはsearchPatternnullの場合はArgumentNullExceptionが発生すると示されています。Microsoft Learn

C#
string? path = null;

foreach (string file in Directory.EnumerateFiles(path!))
{
Console.WriteLine(file);
}

対処法としては、事前にstring.IsNullOrWhiteSpaceでチェックします。

C#
string? path = GetTargetPath();

if (string.IsNullOrWhiteSpace(path))
{
Console.WriteLine("パスが指定されていません。");
return;
}

if (!Directory.Exists(path))
{
Console.WriteLine("指定フォルダが存在しません。");
return;
}

foreach (string file in Directory.EnumerateFiles(path))
{
Console.WriteLine(file);
}

searchPatternもユーザー入力から受け取る場合は、未入力時の既定値を決めておくとよいです。

C#
string? pattern = GetSearchPattern();

if (string.IsNullOrWhiteSpace(pattern))
{
pattern = "*";
}

foreach (string file in Directory.EnumerateFiles(@"C:\Work", pattern))
{
Console.WriteLine(file);
}

9. Directory.EnumerateFilesを使うときの注意点

9-1. 遅延実行のため列挙タイミングで例外が発生する

Directory.EnumerateFilesは、結果をすぐにすべて取得するのではなく、列挙時に順次取得します。そのため、メソッドを呼び出した行ではなく、foreachで列挙している途中に例外が発生することがあります。

C#
var files = Directory.EnumerateFiles(@"C:\Work", "*.txt");

// この時点ではまだ実際の列挙が進んでいない場合がある
foreach (string file in files)
{
Console.WriteLine(file);
}

例外処理は、列挙するforeachを含めて囲むのが基本です。

C#
try
{
foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*.txt"))
{
Console.WriteLine(file);
}
}
catch (Exception ex) when (
ex is IOException ||
ex is UnauthorizedAccessException ||
ex is DirectoryNotFoundException)
{
Console.WriteLine(ex.Message);
}

また、返されたコレクションはキャッシュされません。公式ドキュメントでも、GetEnumeratorを呼び出すたびに新しい列挙が開始されると説明されています。Microsoft Learn

C#
var files = Directory.EnumerateFiles(@"C:\Work");

// 1回目の列挙
foreach (string file in files)
{
Console.WriteLine(file);
}

// 2回目の列挙では、再度ファイルシステムを列挙する
foreach (string file in files)
{
Console.WriteLine(file);
}

同じ結果を何度も使いたい場合は、必要に応じてToList()で固定します。

C#
var files = Directory.EnumerateFiles(@"C:\Work").ToList();

9-2. foreach中にファイルが削除・移動される可能性がある

Directory.EnumerateFilesで取得したファイルが、処理中に削除されたり移動されたりする可能性があります。特に、ログフォルダ、共有フォルダ、一時フォルダ、他プロセスが書き込むフォルダでは起こりやすいです。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Temp"))
{
string text = File.ReadAllText(file);
Console.WriteLine(text);
}

このコードでは、列挙後にファイルが削除されるとFile.ReadAllTextで例外が発生する可能性があります。ファイル単位で例外処理を行うと安全です。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Temp"))
{
try
{
string text = File.ReadAllText(file);
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine($"ファイルが見つかりません: {file}");
}
catch (IOException ex)
{
Console.WriteLine($"読み込みに失敗しました: {file}");
Console.WriteLine(ex.Message);
}
}

ファイルシステムは常に変化する可能性があるため、列挙結果が処理完了まで必ず存在するとは限りません。

9-3. 大量ファイル検索ではメモリ使用量に注意する

Directory.EnumerateFiles自体は大量ファイル処理に向いていますが、使い方によってはメモリ使用量が増えます。

たとえば、次のようにToArray()ToList()を使うと、検索結果をすべてメモリに保持します。

C#
var files = Directory.EnumerateFiles(
@"C:\Data",
"*",
SearchOption.AllDirectories).ToList();

件数が多い場合は、次のように1件ずつ処理するほうがよいです。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Data",
"*",
SearchOption.AllDirectories))
{
ProcessFile(file);
}

また、Count()を呼び出す場合も、すべての結果を列挙する必要があります。

C#
int count = Directory.EnumerateFiles(@"C:\Data").Count();

件数だけが必要なら問題ありませんが、その後もう一度foreachすると再列挙になります。件数と内容の両方を何度も使うなら、リスト化するか、処理設計を見直しましょう。

9-4. ファイル名の大文字・小文字の扱い

ファイル名の大文字・小文字の扱いは、OSやファイルシステム、実行環境によって影響を受けることがあります。Windowsでは一般的に大文字小文字を区別しない扱いが多い一方、Linuxでは区別されることがあります。

拡張子を比較するときは、明示的に大文字小文字を無視すると安全です。

C#
var files = Directory.EnumerateFiles(@"C:\Images")
.Where(file => string.Equals(
Path.GetExtension(file),
".jpg",
StringComparison.OrdinalIgnoreCase));

foreach (string file in files)
{
Console.WriteLine(file);
}

複数拡張子を比較する場合も、StringComparer.OrdinalIgnoreCaseを指定したHashSetを使うと便利です。

C#
var extensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png"
};

var files = Directory.EnumerateFiles(@"C:\Images")
.Where(file => extensions.Contains(Path.GetExtension(file)));

9-5. WindowsとLinuxでパス区切り文字が異なる点

Windowsではパス区切りに\を使い、LinuxやmacOSでは/を使います。C#でクロスプラットフォーム対応を考える場合は、文字列連結でパスを作らず、Path.Combineを使うのが基本です。

C#
string dir = Path.Combine("data", "logs");

Windowsの絶対パスを直接書く場合は、逐語的文字列リテラルを使うと読みやすくなります。

C#
string dir = @"C:\Work\Logs";

通常の文字列として書く場合は、\をエスケープする必要があります。

C#
string dir = "C:\\Work\\Logs";

クロスプラットフォームのアプリでは、次のように基準ディレクトリから組み立てると扱いやすいです。

C#
string baseDir = AppContext.BaseDirectory;
string logDir = Path.Combine(baseDir, "logs");

foreach (string file in Directory.EnumerateFiles(logDir, "*.log"))
{
Console.WriteLine(file);
}

10. Directory.EnumerateFilesに関するよくある質問

10-1. Directory.EnumerateFilesでフォルダも取得できるか

Directory.EnumerateFilesで取得できるのはファイルです。フォルダを取得したい場合は、Directory.EnumerateDirectoriesを使います。

C#
foreach (string dir in Directory.EnumerateDirectories(@"C:\Work"))
{
Console.WriteLine(dir);
}

ファイルとフォルダの両方を取得したい場合は、Directory.EnumerateFileSystemEntriesを使います。

C#
foreach (string entry in Directory.EnumerateFileSystemEntries(@"C:\Work"))
{
Console.WriteLine(entry);
}

ファイルだけならEnumerateFiles、フォルダだけならEnumerateDirectories、両方ならEnumerateFileSystemEntriesと使い分けます。

10-2. ファイル名だけを取得するにはどうするか

Directory.EnumerateFilesはパスを含む文字列を返します。ファイル名だけを取得したい場合は、Path.GetFileNameを使います。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work"))
{
string fileName = Path.GetFileName(file);
Console.WriteLine(fileName);
}

拡張子なしのファイル名だけが必要な場合は、Path.GetFileNameWithoutExtensionを使います。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work"))
{
string name = Path.GetFileNameWithoutExtension(file);
Console.WriteLine(name);
}

拡張子だけを取得する場合は、Path.GetExtensionです。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work"))
{
string extension = Path.GetExtension(file);
Console.WriteLine(extension);
}

10-3. 複数条件でファイル検索できるか

できます。ただし、searchPatternだけで複数条件をすべて表現するのではなく、LINQと組み合わせるのが一般的です。

たとえば、.txtまたは.csvで、ファイル名にreportを含むものだけを検索する例です。

C#
var extensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".txt",
".csv"
};

var files = Directory.EnumerateFiles(@"C:\Work")
.Where(file => extensions.Contains(Path.GetExtension(file)))
.Where(file => Path.GetFileName(file)
.Contains("report", StringComparison.OrdinalIgnoreCase));

foreach (string file in files)
{
Console.WriteLine(file);
}

更新日時やファイルサイズも条件にできます。

C#
DateTime border = DateTime.Now.AddDays(-30);

var files = Directory.EnumerateFiles(@"C:\Work")
.Where(file => Path.GetExtension(file).Equals(".log", StringComparison.OrdinalIgnoreCase))
.Where(file => File.GetLastWriteTime(file) >= border)
.Where(file => new FileInfo(file).Length > 0);

foreach (string file in files)
{
Console.WriteLine(file);
}

10-4. 検索結果の順番は保証されるか

Directory.EnumerateFilesの検索結果の順番に依存しないようにするのが安全です。ファイル名順や更新日時順で処理したい場合は、明示的にOrderByを指定します。

ファイル名順に並べる例です。

C#
var files = Directory.EnumerateFiles(@"C:\Work")
.OrderBy(file => Path.GetFileName(file));

foreach (string file in files)
{
Console.WriteLine(file);
}

更新日時の新しい順に並べる例です。

C#
var files = Directory.EnumerateFiles(@"C:\Work")
.OrderByDescending(file => File.GetLastWriteTime(file));

foreach (string file in files)
{
Console.WriteLine($"{File.GetLastWriteTime(file)} {file}");
}

ただし、OrderByを使うと並び替えのために結果を保持する必要があります。大量ファイルを扱う場合は、メモリ使用量にも注意しましょう。

10-5. 非同期でファイル検索できるか

Directory.EnumerateFiles自体は非同期メソッドではありません。await Directory.EnumerateFiles(...)のようには書けません。

ただし、列挙したファイルに対する読み込み処理は非同期にできます。

C#
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
foreach (string file in Directory.EnumerateFiles(@"C:\Logs", "*.txt"))
{
string text = await File.ReadAllTextAsync(file);
Console.WriteLine(text);
}
}
}

大量ファイルを非同期的に処理したい場合は、TaskChannelParallel.ForEachAsyncなどを組み合わせる方法もあります。

C#
await Parallel.ForEachAsync(
Directory.EnumerateFiles(@"C:\Logs", "*.log"),
async (file, cancellationToken) =>
{
string text = await File.ReadAllTextAsync(file, cancellationToken);
Console.WriteLine($"{file}: {text.Length}文字");
});

ただし、並列度を上げすぎるとディスクI/Oに負荷がかかります。必要に応じて並列数を制限しましょう。

C#
var options = new ParallelOptions
{
MaxDegreeOfParallelism = 4
};

await Parallel.ForEachAsync(
Directory.EnumerateFiles(@"C:\Logs", "*.log"),
options,
async (file, cancellationToken) =>
{
string text = await File.ReadAllTextAsync(file, cancellationToken);
Console.WriteLine($"{file}: {text.Length}文字");
});

まとめ

Directory.EnumerateFilesは、C#でフォルダ内のファイル一覧を取得するための便利なメソッドです。指定フォルダ内のファイルをIEnumerable<string>として取得でき、foreachやLINQと組み合わせて柔軟に処理できます。

基本的な使い方は、次のようにシンプルです。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work"))
{
Console.WriteLine(file);
}

拡張子で絞り込む場合は、searchPatternを指定します。

C#
foreach (string file in Directory.EnumerateFiles(@"C:\Work", "*.txt"))
{
Console.WriteLine(file);
}

サブフォルダも含めて再帰検索する場合は、SearchOption.AllDirectoriesを使います。

C#
foreach (string file in Directory.EnumerateFiles(
@"C:\Work",
"*.txt",
SearchOption.AllDirectories))
{
Console.WriteLine(file);
}

Directory.GetFilesとの大きな違いは、EnumerateFilesが順次取得、GetFilesが配列として一括取得である点です。大量ファイルを処理する場合は、全件取得を待たずに処理を始められるDirectory.EnumerateFilesが向いています。Microsoft Learnでも、EnumerateFilesは全体が返される前に列挙を開始でき、多くのファイルやディレクトリを扱う場合に効率的だと説明されています。Microsoft Learn

一方で、Directory.EnumerateFilesは遅延実行のため、例外が列挙中に発生することがあります。存在しないフォルダ、アクセス権限のないフォルダ、長すぎるパス、処理中に削除されたファイルなどに注意し、必要に応じてDirectory.Existstry-catchEnumerationOptionsを使いましょう。

C#でファイル検索やファイル処理を行うなら、Directory.EnumerateFilesは必ず押さえておきたいメソッドです。GetFilesとの違いを理解し、ファイル数や処理内容に応じて使い分けることで、効率的で安全なファイル操作を実装できます。