【実践】C#ファイル/フォルダ操作術。すぐに使えるサンプルコード付き

当ページのリンクには広告が含まれています。

C#でファイルやフォルダ操作を行う代表的なクラスとして、System.IO.File、System.IO.Directory、System.IO.Pathがあります。

今回は、これらクラスの役割について図で整理したうえで、やりたいこと別にメソッドの使い方を、サンプルコードを付けて解説していきます。

また最後の章では、これらを使った実用サンプルも掲載しています。実用サンプルは自由にコピペ可能ですので、ご自身の用途に合わせて修正の上、ご活用ください。

C#によるファイルの読み書きの詳細については、「【C#】ファイルの読み書き基礎と実践。コピペで使える実用サンプル満載!」で紹介していますので、こちらも併せてご覧下さい。

目次

ファイル/フォルダ操作クラスの全体像

.NET でファイルやフォルダを扱うクラスは3種類用意されていますが、それぞれ次の役割を持っています。

クラス名役割
System.IO.Fileファイルの作成、コピー、削除、移動などの基本的な操作を行うためのクラスです。ファイルの読み書きも行えます。
System.IO.Directoryディレクトリ(フォルダ)の作成、削除、移動などの操作を行うためのクラスです。
System.IO.Pathファイルパスやディレクトリパスの操作を行うためのクラスです。パスの結合や解析、拡張子の取得などが可能です。

用途別クラス・メソッド紹介

今回紹介する3つのクラスは、全て System.IO 名前空間に含まれていますので、ここからは記述を省略しています。

ファイルやフォルダの存在確認

存在確認は Fileクラス、Directoryクラス、Pathクラスそれぞれに用意されていますが、下表の通り判定結果が異なります。

機能クラス・メソッド説明
ファイルの存在確認bool File.Exists(string path)path がファイルならtrue、
それ以外ならfalseを返します。
フォルダの存在確認bool Directory.Exists(string path)path がフォルダならtrue、
それ以外ならfalseを返します。
ファイル又はフォルダの存在確認bool Path.Exists(string path)path がファイル又はフォルダならtrue
それ以外ならfalseを返します。
// ファイルの存在確認
if File.Exists(@"d:\hoge.txt"))
{
    Console.WriteLine("ファイルが存在します");
}

// フォルダの存在確認
if Directory.Exists(@"d:\myfolder"))
{
    Console.WriteLine("フォルダが存在します");
}

フォルダやファイルの新規作成

Fileクラスを使うと文字列や配列の中身を簡単にファイルに出力することが出来ます。
また、フォルダについても作成したいフォルダのパスを指定するだけす。

パスは "D:\AAA\BBB\CCC\DDD" の様に階層を含めて指定することが可能で、この場合は最後のDDDに到達する過程のフォルダも自動的に作成されます。

機能クラス・メソッド説明
ファイルの作成void File.WriteAllText(
string path,
string contents,
Encoding encoding
)
contents で指定した文字列をファイルに保存します。
文字コードは encoding で指定しますが、省略すると UTF-8として保存されます。
void File.WriteAllLines(
string path,
IEnumerable<string> lines,
Encoding encoding
)
linesで指定した文字列配列(string[] 又は List<string>)をファイルに保存します。
文字コードは encoding で指定しますが、省略すると UTF-8として保存されます。
フォルダの作成DirectoryInfo Directory.CreateDirectory(
string path
)
指定されたパスにフォルダを作成します。
パスに階層を指定すると、その階層までフォルダが作成されます。
既に存在すれば何もしません。
フォルダの有無に関わらず、DirectoryInfoを返します。

フォルダ又はファイルのリネーム

Renameなど名前を変更するメソッドが無いので、ファイル/フォルダを移動するためのMoveメソッドを代用して名前変更します。

既にファイルやフォルダが存在する場合は次の例外が発生します。
  Cannot create 'o:\test' because a file or directory with the same name already exists.'

機能クラス・メソッド説明
ファイルの名前変更void File.Move(
string sourceName,
string destName
)
ファイル名 sourceName を destName に変更します。
フォルダの名前変更void Directory.Move(
string ourceName,
string destName
)
フォルダ名 sourceName を destName に変更します。
// Dドライブの hoge.csv ファイルを mage.csv に名前変更する
File.Move(@"d:\hoge.csv",@"d:\mage.csv");

// Dドライブの images フォルダを backup フォルダに名前変更する
Directory.Move(@"d:\images", @"d:\backup");

ファイル又はフォルダのコピー

ファイルはコピーできますが、フォルダを丸ごとコピーするメソッドは用意されていません。フォルダ階層丸ごとのサンプルコードを掲載しましたので、併せてご活用ください。

機能クラス・メソッド説明
ファイルの名前変更void File.Copy(
string sourcePath,
string destPath,
bool overWrite
)
sourcePath で指定したファイルを destPathで指定した場所にコピーします。
overWrite は省略可能で、初期値は false になっています。既に存在した時は上書きしたい場合は true を指定します。
フォルダの名前変更該当なしフォルダ階層ごとコピーできるメソッドが存在しないので、後述する自作メソッドを使う必要があります。
// Dドライブ直下の hoge.txt を Eドライブ直下に mage.txt という名前でコピーする
File.Copy(@"d:\hoge.txt", @"e:\mage.txt");

// Dドライブ直下の hoge.txt を、Eドライブ直下のCSVフォルダに mage.txt という名前でコピーする  
File.Copy(@"d:\hoge.txt", @"e:\csv\mage.txt");

マイクロソフトの公式サイトにサンプルが掲載されていますが、コピー元フォルダにアクセス権が無いフォルダが含まれると例外が発生して処理が止まるため、例外処理を実装したサンプルを紹介しておきます。

// フォルダを再帰的にコピーするメソッド
void CopyDirectory(string sourceDir, string destDir)
{
    // コピー先のフォルダが存在しない場合は作成する
    if (!Directory.Exists(destDir))
    {
        Directory.CreateDirectory(destDir);
    }

    // コピー元フォルダ内のファイルをコピーする
    foreach (string file in Directory.GetFiles(sourceDir))
    {
        try
        {
            string destFile = Path.Combine(destDir, Path.GetFileName(file));
            File.Copy(file, destFile, true); // ファイルをコピーする。既に存在すれば上書きする。
        }
        catch (Exception ex)
        {
            // エラーが発生した場合はエラーメッセージを表示して処理を継続する
            Console.WriteLine("ファイルのコピー中にエラーが発生しました: " + ex.Message);
        }
    }

    // コピー元フォルダ内のサブフォルダを再帰的に処理する
    foreach (string folder in Directory.GetDirectories(sourceDir))
    {
        try
        {
            string destFolder = Path.Combine(destDir, Path.GetFileName(folder));
            CopyDirectory(folder, destFolder); // サブフォルダを再帰的にコピーする
        }
        catch (Exception ex)
        {
            // エラーが発生した場合はエラーメッセージを表示して処理を継続する
            Console.WriteLine("フォルダのコピー中にエラーが発生しました: " + ex.Message);
        }
    }
}

ファイル又はフォルダの削除(ゴミ箱に残す方法を含む)

ファイル、フォルダともに削除メソッドが用意されており、フォルダの削除は配下の階層ごと削除が可能です。尚、メソッドを使った削除は込み箱に残らないのでご注意ください。

もしゴミ箱に残したい場合は、後述する VisualBasic.FiieIO名前空間のDeleteメソッドを使う必要があります。

機能クラス・メソッド説明
ファイルの削除void File.Delete(
string path
)
指定されたファイルを削除します。
フォルダの削除void Directory.Delete(
string path,
bool recursive
)
指定されたフォルダを削除します。
第2引数の recursive を省略すると、空のフォルダしか削除できません(省略時は false が指定される)。
フォルダの中身を階層ごと削除する場合は true を指定します。
// Dドライブ直下の imageフォルダにある hoge.img を削除する
File.Delete(@"d:\images\hoge.img");

// Dドライブ直下のbackup フォルダを、配下の階層を含めて全て削除する
Directory.Delete(@"d:\backup");

削除した内容をゴミ箱に残すには、Microsoft.VisualBasic.FileIO名前空間にある DeleteFIle、DeleteDirectory メソッドを使います。

using Microsoft.VisualBasic.FileIO;

// Dドライブの hoge.txt ファイルをゴミ箱に入れる
FileSystem.DeleteFile(@"D:\hoge.txt", UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);


// Dドライブ直下のbackupフォルダを丸ごとゴミ箱に入れる
FileSystem.DeleteDirectory(@"D:\backup", UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);

ファイル又はフォルダの移動

名前変更で紹介したMoveメソッドを使ってファイルやフォルダを移動します。

ただし、Windowsでは同じドライブ内での移動しかサポートされていません。仮に CドライブからDドライブに移動するような場合、以下の例外が発生します。

 System.IO.IOException: 'Source and destination path must have identical roots. Move will not work across volumes.'

異なるドライブ間での移動はCopyとDeleteを組み合わせて実現します(詳細は後述)。

機能クラス・メソッド説明
ファイルの移動void File.Move(
string sourcePath,
string destPath
)
ファイルを sourceName から destName に移動します。
フォルダの移動void Directory.Move(
string sourcePath,
string destPath
)
フォルダを sourceName から destName にフォルダ階層ごと移動します。

# d:ドライブの hoge.csv ファイルを dataフォルダ直下に移動する
File.Move(@"d:\hoge.csv",@"d:\data\hoge.csv");

# d:ドライブの hoge.csv ファイルを dataフォルダ直下にmage.txt という名前に変えて移動する
File.Move(@"d:\hoge.csv",@"d:\data\mage.txt");

# d:ドライブの images フォルダを backup フォルダ直下に移動する
Directory.Move(@"d:\images", @"d:\backup\images");

# d:ドライブの images フォルダを backup フォルダ直下に images2 という名前に変えて移動する
Directory.Move(@"d:\images", @"d:\backup\images2");

異なったドライブに移動したい場合は、Copy メソッドと Delete メソッドを組み合わせて実現します。Copyメソッドは戻り値が無いので、成功したか否かが判定できません。

そこで、例外処理の try~catch で囲み、その中にCopy と Delete を続けて実行することで、コピーが成功した時のみ元のファイルが削除されるようにしています。

# 異なるドライブ間でのファイル移動
try
{
    File.Copy(sourName, destName );
    File.Delete(sourName);
}
catch {}

フォルダについては階層ごとのコピーが出来ないので、前述「ファイル又はフォルダのコピー」で紹介した自作メソッドを try~catchの中で呼ぶことで実現します。

# 異なるドライブ間でのファイル移動
try
{
    CopyDirectory(sourPath, destPath );
    Directory.Delete(sourPath);
}
catch {}

この方法では、フォルダコピーが完了しないと元のフォルダは削除されません。
これはある意味安全ではありますが、Windows標準のエクスプローラーでフォルダを移動する時の様に、途中で失敗してもそれまでに移動したものが元から消したい場合もあるでしょう。

その場合は、File.Copy(file, destFile, true) の部分を try~catch で囲み、そのかに File.Delete も一緒に入れておくことで対応が可能です。

ファイルまたはフォルダ一覧の取得(参照権限が無いフォルダを含む)

機能クラス・メソッド説明
ファイルの移動string[] Directory.GetFiless(
string path,
string serchPattern,
SearchOption searchOption
)
指定した場所にあるファイルの一覧を取得します。
serchPattern、searchOption 共に省略可能です。
searchPattern にワイルドカードを指定することで、パターンに一致するフォルダのみ取得可能です。
searchOption に AllDirectories を指定するとフォルダ階層下を含む全てのファイルが取得可能です。
フォルダの移動string[] Directory.GetDirectories(
string path,
string serchPattern,
SearchOption searchOption
)
指定した場所にあるフォルダの一覧を取得します。
serchPattern、searchOption 共に省略可能です。
searchPattern にワイルドカードを指定することで、パターンに一致するフォルダのみ取得可能です。
searchOption に AllDirectories を指定するとフォルダ階層下を含む全てのフォルダが取得可能です。
// Dドライブ直下の Imagesフォルダに存在し、拡張子が .pngのファイルを、フォルダを階層下を含めて検索する
var list = Directory.GetFiles(@"D:\Images", "*.png",SearchOption.AllDirectories);

D:\Images\00388-846986118.png
D:\Images\00389-846986119.png
D:\Images\00390-846986120.png
D:\Images\00395-2819340012.png
D:\Images\Backup\00396.png
D:\Images\Backup\風景.png
D:\Images\Resize\Small\IMG01250.png
D:\Images\Resize\Small\IMG01255.png

// Dドライブ直下の backupフォルダにある im から始まるフォルダを階層下を含めて検索する
var list = Directory.GetFiles(@"D:\backup", "im*",SearchOption.AllDirectories);

D:\backup\images
D:\backup\images2
D:\backup\images3
D:\backup\images\ImageResize
D:\backup\images\ImgList

指定したパスの全階層からファイルを取得する場合、その中にアクセス権限が無いフォルダが存在すると、次の例外が発生します。例えばCドライブやDドライブのルートを指定すると、ほぼ確実に次の例外が発生します。

System.UnauthorizedAccessException: 'Access to the path 'P:\System Volume Information' is denied.'

これを回避するためには、フォルダ1つ1つに対してアクセスしつつ、例外が発生した時は無視するような処理が必要です。

下記は、検索対象に権限が無いフォルダが含まれていた場合、それを無視してファイル一覧を取得するサンプルコードです。IEnumerable<string>型の戻り値なので、 foreachループで1つづつ受け取るか、.ToArray() でまとめて受け取るかしてください。

/// <summary>
/// 指定されたパス内のファイルの一覧を取得します。サブフォルダを再帰的に検索するかどうかを指定できます。
/// </summary>
/// <param name="path">ファイルの一覧を取得するパス</param>
/// <param name="searchPattern">ファイル名のフィルター</param>
/// <param name="searchOption">サブフォルダを再帰的に検索するかどうかを指定します</param>
/// <returns>パス内のファイルの一覧</returns>

IEnumerable<string> GetFiles(string path, string searchPattern, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
    List<string> files = new List<string>();

    // パス内のファイルを取得
    DirectoryInfo diTop = new DirectoryInfo(path);
    try
    {
        // 指定されたパス直下のファイル一覧を取得
        foreach (var info in diTop.EnumerateFiles(searchPattern))
        {
            // Try~Catch内でyield return 出来ないので、一旦Listに格納
            files.Add(info.FullName);
        }
    }
    catch { }

    // 取得したファイルを列挙して返す
    foreach (var file in files)
    {
        yield return file;
    }

    // サブフォルダを検索する場合
    if (searchOption == SearchOption.AllDirectories)
    {
        // 指定されたパス直下にある全てのフォルダ一覧を取得し、1フォルダづつループ処理
        foreach (var di in diTop.EnumerateDirectories("*"))
        {
            files.Clear();

            try
            {
                // フォルダ直下にあるファイルの一覧を取得
                foreach (var info in di.EnumerateFiles(searchPattern, SearchOption.AllDirectories))
                {
                    // Try~Catch内でyield return 出来ないので、一旦Listに格納
                    files.Add(info.FullName);
                }
            }
            catch { }

            foreach (var file in files)
            {
                // 取得したファイルを列挙して返す
                yield return file;
            }
        }
    }
}
// ファイル一覧をまとめて受け取る場合
string[] files = GetFiles(@"D:\", "*", SearchOption.AllDirectories)).ToArray();

// ファイル一覧をループ内で処理する場合
foreach (var file in GetFiles(@"D:\", "*", SearchOption.AllDirectories))
{
    Console.WriteLine(file);
}

ファイルパスの操作(フォルダ名やファイル名の分割、結合)

機能クラス・メソッド説明
フォルダ名の分離string Path.GetDirectoryName(
string path
)
指定されたパス文字列のフォルダ名部分を取得します。ファイル名と拡張子は含まれません。
ファイル名の分離string Path.GetFileName(
string path
)
指定されたパス文字列のファイル名と拡張子を取得します。フォルダ部分は含まれません。
ファイル名の取得string Path.GetFileNameWithoutExtension(
string path
)
指定されたパス文字列の拡張子を除いたファイル名を取得します。ディレクトリ部分は含まれません。
拡張子の取得string Path.GetExtension(
string path
)
指定されたパス文字列の拡張子部分を取得します。先頭にピリオド '.' が付加されます。
例 :".jpg"
パスのルートの取得string Path.GetPathRoot(
string path
)
指定されたパス文字列のルートディレクトリ(ドライブ名)を取得します。
例:"d:\"
フォルダ名の結合string Path.Combine(
string path1,string path2,・・・
)
複数の文字列からパスを構築します。各部分が自動的に適切に連結され、重複するディレクトリ区切り記号は除去されます。

ファイル、フォルダ操作の実用事例

ワイルドカードに一致するファイルを対象にフォルダを丸ごとコピー

CopyFilesWithFilterメソッドは、sourceFolderで指定されたフォルダ階層を丸ごとdestinationFolderで指定されたフォルダ配下にコピーする関数です。

CopyFilesWithFilter(sourceFolder, destinationFolder ,wildcardPattern,filterFunc=null)

特徴としては、第3引数にワイルドカードが指定でき、かつ コールバック関数を用いて filterFunc フィルター条件が指定できる点です。

using System;
using System.Collections.Generic;
using System.IO;

public class FileUtil
{
    /// <summary>
    /// 指定したフォルダ階層ごと、ワイルドカードに一致し、フィルタ関数に一致するファイルを別の階層にコピーします。
    /// </summary>
    /// <param name="sourceFolder">コピー元のフォルダのパス。</param>
    /// <param name="destinationFolder">コピー先のフォルダのパス。</param>
    /// <param name="wildcardPattern">ワイルドカードパターン。</param>
    /// <param name="filterFunc">フィルタ条件を指定するコールバック関数。デフォルトは null。</param>
    public void CopyFilesWithFilter(string sourceFolder, string destinationFolder, string wildcardPattern, Func<string, bool> filterFunc = null)
    {
        // ワイルドカードに一致するファイルを取得
        string[] matchingFiles = Directory.GetFiles(sourceFolder, wildcardPattern, SearchOption.AllDirectories);

        // コピー先のフォルダ構造を再現しながらファイルをコピーする
        foreach (string filePath in matchingFiles)
        {
            // フィルタ関数が指定されており、その関数が false を返す場合はスキップする
            if (filterFunc != null && !filterFunc(filePath))
            {
                continue;
            }

            // コピー元のファイルパスを取得
            string fileRelativePath = Path.GetRelativePath(sourceFolder, filePath);
            string destinationPath = Path.Combine(destinationFolder, fileRelativePath);

            // コピー先のディレクトリを作成
            Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));

            // ファイルをコピー
            File.Copy(filePath, destinationPath, true);

        }
    }

次のソースコードはコールバック関数のサンプルです。コピー対象となるファイルの最終更新日時が、直近5日以降になら true、以前なら falseを返します。これをCopyFilesWithFilterの第4引数に指定することで、指定日時より以降に作成されたファイルのみコピーすることができます。

using System.IO;
using System.Text;

var util = new FileUtil();
util.CopyFilesWithFilter(@"d:\images", @"e:\backup\images", "*.png", func);
 
// コールバック関数(フィルター条件)
bool func(string path)
{
    TimeSpan threshold = TimeSpan.FromDays(5); // 現在時刻から5日前
    DateTime lastWriteTime = File.GetLastWriteTime(path); // ファイルの最終更新時刻を取得
    return (DateTime.Now - threshold) <= lastWriteTime ; // 5日前より後に更新されているか


};

2つのフォルダを比較して、それぞれの差分を抽出する

FolderDiff メソッドは、sourceFolderで指定されたフォルダと、targetFolderで指定フォルダに対して、階層下にある全てのファイルを比較し、それぞれの差分をタプルで返します。

var result = FolderDiff(sourceFolder, targetFolder,wildcardPattern)

戻り値は、元のフォルダにのみ存在するファイル(uniqueSourceFiles)のリストと、比較先のフォルダにのみ存在するファイル(uniqueTargetFiles)のリストです。

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

public class FileUtil
{
    /// <summary>
    /// 指定された2つのフォルダ間の差分を抽出します。
    /// </summary>
    /// <param name="sourceFolder">比較元のフォルダのパス。</param>
    /// <param name="targetFolder">比較先のフォルダのパス。</param>
    /// <param name="wildcardPattern">ワイルドカード文字列(デフォルトは '*')。</param>
    /// <returns>比較元にのみ存在するファイルのリストと比較先にのみ存在するファイルのリストのタプル。</returns>
    public (List<string> uniqueSourceFiles, List<string> uniqueTargetFiles) FolderDiff(string sourceFolder, string targetFolder, string wildcardPattern = "*")
    {
        // 比較元フォルダ内のファイルを取得
        var sourceFiles = Directory.GetFiles(sourceFolder, wildcardPattern, SearchOption.AllDirectories)
                                    .Select(file => Path.GetRelativePath(sourceFolder, file));

        // 比較先フォルダ内のファイルを取得
        var targetFiles = Directory.GetFiles(targetFolder, wildcardPattern, SearchOption.AllDirectories)
                                    .Select(file => Path.GetRelativePath(targetFolder, file));

        // 比較元にのみ存在するファイルを抽出
        var uniqueSourceFiles = sourceFiles.Except(targetFiles).ToList();

        // 比較先にのみ存在するファイルを抽出
        var uniqueTargetFiles = targetFiles.Except(sourceFiles).ToList();

        // 結果を返す
        return (uniqueSourceFiles, uniqueTargetFiles);
    }
}

FolderDiffメソッドの使い方は次の通りです。このサンプルコードでは、Dドライブ直下のimages フォルダと Eドライブ直下にある backup フォルダの差分を抽出しています。

using System.IO;
using System.Text;


// 2つのフォルダの差分ファイル抽出
var util = new FileUtil();
var res = util.FolderDiff(@"D:\images", @"E:\backup");


// 元フォルダにのみ存在するファイルの一覧
foreach (var file in res.uniqueSourceFiles)
{
    Console.WriteLine(file);
}


// ターゲットフォルダにのみ存在するファイル一覧
foreach (var file in res.uniqueTargetFiles)
{
    Console.WriteLine(file);
}

まとめ

今回は、C#でファイルやフォルダ操作を行う代表的な3つのクラス、System.IO.File、System.IO.Directory、System.IO.Path について、やりたい事にメソッドを整理して紹介しました。

フォルダ階層丸ごとのコピーや移動については .NET 標準のメソッドが用意されていないため、本記事ではコピペで利用可能な関数のサンプルとして具体的なソースコードを紹介しました。

また、フォルダ階層化を含むファイル一覧の取得についても、アクセス権限が無いフォルダが含まれていても無視する(例外を発生させない)サンプルコードを紹介しました。

System.IO.File、System.IO.Directory、System.IO.Path に実装されているメソッドは、今回紹介したもの以外にも数多く存在し、また 紹介していないクラスも多々ありますが、今回紹介したメソッドさえ理解していれば、よほど特殊なことをしない限り困ることは無いかと思います。

今回の記事が皆さんのお役に立てば幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次