はじめに なぜ「指定拡張子検索」が業務で重要なのか
業務システムでは、「.csv だけ集めてバッチ処理したい」「.log だけを圧縮したい」「.bak だけをバックアップフォルダに移動したい」といった、「特定の拡張子だけを対象にしたい」場面が本当に多いです。
毎回エクスプローラーで目視して選ぶのは現実的ではありませんし、拡張子を間違えて処理すると、想定外のファイルを壊してしまうリスクもあります。
C# では、Directory.GetFiles や Directory.EnumerateFiles を使うことで、「指定拡張子だけを検索して一覧を取得する」処理を簡単に書けます。
ここでは、プログラミング初心者向けに、基本の API から、単一拡張子・複数拡張子・再帰検索・実務ユーティリティ化まで、かみ砕いて解説していきます。
基本の API Directory.GetFiles と検索パターン
Directory.GetFiles の基本形
まずは、「指定フォルダの中から、特定の拡張子だけを検索する」一番シンプルな例から見てみます。
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#Directory.GetFiles(path, searchPattern) の searchPattern に "*.csv" を指定することで、「.csv で終わるファイルだけ」を取得できます。* は「0 文字以上の任意の文字列」を表すワイルドカードで、*.csv は「拡張子が .csv のすべてのファイル」という意味になります。
ここで押さえておきたい重要ポイントは、「拡張子検索は searchPattern で行う」「*.拡張子 という形で指定する」という二つです。
サブフォルダも含めて検索する SearchOption
業務では、「指定フォルダ直下だけでなく、サブフォルダも含めて全部探したい」ことがよくあります。
その場合は、SearchOption.AllDirectories を使います。
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\logs";
string[] logFiles = Directory.GetFiles(
dir,
"*.log",
SearchOption.AllDirectories);
foreach (string file in logFiles)
{
Console.WriteLine(file);
}
}
}
C#SearchOption.AllDirectories を指定すると、「指定フォルダ配下のすべてのサブフォルダを再帰的に検索」してくれます。
逆に、「直下だけでよい」場合は SearchOption.TopDirectoryOnly(省略時のデフォルト)になります。
実務で使えるユーティリティ 単一拡張子検索
単一拡張子を検索するユーティリティメソッド
毎回 Directory.GetFiles を直接書くのではなく、「指定拡張子のファイル一覧を取得する」ユーティリティを用意しておくと、コードが読みやすくなります。
using System;
using System.IO;
public static class FileSearchUtil
{
public static string[] GetFilesByExtension(
string directoryPath,
string extension,
bool includeSubDirectories = false)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"ディレクトリが見つかりません: {directoryPath}");
}
if (!extension.StartsWith("."))
{
extension = "." + extension;
}
string pattern = "*" + extension;
SearchOption option = includeSubDirectories
? SearchOption.AllDirectories
: SearchOption.TopDirectoryOnly;
return Directory.GetFiles(directoryPath, pattern, option);
}
}
C#ここでの重要ポイントを整理します。
第一に、「拡張子は .付きでも .なしでも受け取れるようにして、内部で統一している」ことです。呼び出し側は "csv" でも ".csv" でも渡せます。
第二に、「includeSubDirectories フラグで、サブフォルダを含めるかどうかを切り替えている」ことです。業務によって「直下だけ」「配下全部」が切り替えられるのはとても便利です。
使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\data";
string[] csvFiles = FileSearchUtil.GetFilesByExtension(
dir,
".csv",
includeSubDirectories: true);
Console.WriteLine("CSV ファイル一覧:");
foreach (string file in csvFiles)
{
Console.WriteLine(file);
}
}
}
C#これで、「指定拡張子検索」を一行で表現できるようになります。
複数拡張子をまとめて検索する
Directory.GetFiles を複数回呼ぶシンプルな方法
「.csv と .tsv の両方を対象にしたい」「.jpg と .png をまとめて探したい」といった、複数拡張子の検索もよくあります。
一番シンプルなやり方は、「拡張子ごとに GetFiles を呼び、結果を結合する」方法です。
using System;
using System.IO;
using System.Collections.Generic;
public static class FileSearchUtil
{
public static string[] GetFilesByExtensions(
string directoryPath,
string[] extensions,
bool includeSubDirectories = false)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"ディレクトリが見つかりません: {directoryPath}");
}
SearchOption option = includeSubDirectories
? SearchOption.AllDirectories
: SearchOption.TopDirectoryOnly;
var result = new List<string>();
foreach (string extRaw in extensions)
{
string ext = extRaw;
if (!ext.StartsWith("."))
{
ext = "." + ext;
}
string pattern = "*" + ext;
string[] files = Directory.GetFiles(directoryPath, pattern, option);
result.AddRange(files);
}
return result.ToArray();
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\images";
string[] exts = { ".jpg", ".png" };
string[] imageFiles = FileSearchUtil.GetFilesByExtensions(
dir,
exts,
includeSubDirectories: true);
Console.WriteLine("画像ファイル一覧:");
foreach (string file in imageFiles)
{
Console.WriteLine(file);
}
}
}
C#この方法の良いところは、「ワイルドカード検索の仕組みはそのまま使い、拡張子の数だけ繰り返すだけ」というシンプルさです。
拡張子のリストを設定ファイルや DB から読み込むようにすれば、「運用側で対象拡張子を差し替えられる」柔軟な仕組みにもできます。
LINQ でフィルタする方法(応用)
もう一つの書き方として、「一旦全部のファイルを列挙してから、拡張子でフィルタする」という方法もあります。
using System;
using System.IO;
using System.Linq;
public static class FileSearchUtilLinq
{
public static string[] GetFilesByExtensionsLinq(
string directoryPath,
string[] extensions,
bool includeSubDirectories = false)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"ディレクトリが見つかりません: {directoryPath}");
}
SearchOption option = includeSubDirectories
? SearchOption.AllDirectories
: SearchOption.TopDirectoryOnly;
var normalizedExts = extensions
.Select(e => e.StartsWith(".") ? e.ToLower() : ("." + e.ToLower()))
.ToArray();
return Directory
.GetFiles(directoryPath, "*.*", option)
.Where(path =>
{
string ext = Path.GetExtension(path).ToLower();
return normalizedExts.Contains(ext);
})
.ToArray();
}
}
C#この方法のメリットは、「拡張子の比較を完全に自分で制御できる」ことです。
例えば、「大文字小文字を無視する」「特定のフォルダを除外する」など、柔軟な条件を組み合わせやすくなります。
パフォーマンスと 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, "*.csv", SearchOption.AllDirectories))
{
Console.WriteLine(file);
}
}
}
C#初心者向けのざっくりした指針としては、「件数が少ないうちは GetFiles で十分」「大量のファイルを扱うようになったら EnumerateFiles も検討する」と覚えておけば大丈夫です。
例外とエラー処理を意識した指定拡張子検索
起こり得る例外
指定拡張子検索では、次のような理由で例外が発生する可能性があります。
ディレクトリが存在しない。
権限がなくてフォルダの中身を列挙できない。
ネットワークドライブの接続が切れている。
これらはすべて、「検索できなかった理由」です。
実務では、「どのフォルダで、どんな理由で失敗したか」をログに残しておくと、後から調査しやすくなります。
呼び出し側での例外処理の例
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\data";
try
{
string[] csvFiles = FileSearchUtil.GetFilesByExtension(
dir,
".csv",
includeSubDirectories: true);
Console.WriteLine("CSV ファイル一覧:");
foreach (string file in csvFiles)
{
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 に *.拡張子 を渡して単一拡張子を検索する基本を押さえること。SearchOption で「直下だけ」か「サブフォルダも含めるか」を切り替えられるようにすること。
単一拡張子版と複数拡張子版のユーティリティを用意し、拡張子リストを外部設定から差し替えられるようにしておくこと。
大量ファイルを扱う可能性があるなら、EnumerateFiles を使ったストリーミング処理も視野に入れること。
例外や失敗をログに残し、「どのフォルダで何が起きたか」を後から追えるようにしておくこと。
ここまで押さえておけば、「対象外のファイルを誤って処理してしまう」「必要なファイルを取りこぼす」といったトラブルをかなり減らせます。
