C# Tips | ファイル・ディレクトリ操作:指定拡張子検索

C sharp C#
スポンサーリンク

はじめに なぜ「指定拡張子検索」が業務で重要なのか

業務システムでは、「.csv だけ集めてバッチ処理したい」「.log だけを圧縮したい」「.bak だけをバックアップフォルダに移動したい」といった、「特定の拡張子だけを対象にしたい」場面が本当に多いです。
毎回エクスプローラーで目視して選ぶのは現実的ではありませんし、拡張子を間違えて処理すると、想定外のファイルを壊してしまうリスクもあります。

C# では、Directory.GetFilesDirectory.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 を使ったストリーミング処理も視野に入れること。
例外や失敗をログに残し、「どのフォルダで何が起きたか」を後から追えるようにしておくこと。

ここまで押さえておけば、「対象外のファイルを誤って処理してしまう」「必要なファイルを取りこぼす」といったトラブルをかなり減らせます。

タイトルとURLをコピーしました