C# Tips | ファイル・ディレクトリ操作:ファイル一覧取得

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

はじめに なぜ「ファイル一覧取得」が業務の入り口になるのか

業務システムでファイルを扱う処理は、ほぼ必ず「まず対象フォルダのファイル一覧を取る」ところから始まります。
たとえば「このフォルダの CSV を全部読み込む」「ログファイルを日付順に並べて古いものから削除する」「バックアップ対象のファイルを一括コピーする」など、どれも最初の一歩は「一覧を取る」です。

だからこそ、「ファイル一覧取得」をきちんとユーティリティとして押さえておくと、その上に乗る処理(コピー・削除・圧縮・解析など)がとても書きやすくなります。
ここでは、C# 初心者向けに、Directory.GetFilesDirectory.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 を使ったストリーミング的な一覧取得も視野に入れること。
例外や失敗をログに残し、「どのフォルダで何が起きたか」を後から追えるようにしておくこと。

ここまで押さえておけば、「対象ファイルの取りこぼし」「想定外のファイルを巻き込む」といったトラブルをかなり減らせます。

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