C# Tips | ファイル・ディレクトリ操作:ディレクトリ削除

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

はじめに なぜ「ディレクトリ削除」は特に慎重さが必要なのか

業務システムでは、古いバックアップフォルダを消す、一時作業用フォルダを片付ける、処理に失敗した作業領域をクリーンアップするなど、「フォルダごと削除する」場面が必ず出てきます。
ただし、ディレクトリ削除は「中にあるファイルやサブフォルダもまとめて消える」ことが多く、設計や実装を誤ると「必要なデータを丸ごと消してしまった」という重大事故につながります。

C# では System.IO.Directory.Delete を使ってディレクトリ削除を行いますが、その前後のチェックやルール設計がとても重要です。
ここでは、プログラミング初心者向けに、基本から実務で使えるユーティリティ化まで、丁寧にかみ砕いて解説していきます。


基本のメソッド Directory.Delete を理解する

Directory.Delete の最もシンプルな使い方

まずは、Directory.Delete の一番シンプルな例を見てみましょう。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string folderPath = @"C:\temp\work";

        Directory.Delete(folderPath);

        Console.WriteLine("ディレクトリ削除を実行しました。");
    }
}
C#

このコードは、「C:\temp\work というディレクトリを削除する」という意味です。
ここで重要なのは、この形の Directory.Delete は「中身が空のディレクトリでないと削除できない」という点です。
中にファイルやサブフォルダが残っていると、IOException が発生して失敗します。

つまり、空でないディレクトリを削除したい場合は、別のオプションを使う必要があります。

中身ごと削除するオプション recursive

Directory.Delete には、第二引数に bool recursive を渡せるオーバーロードがあります。
これを true にすると、「中のファイルやサブフォルダも含めて、ディレクトリを丸ごと削除する」動きになります。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string folderPath = @"C:\temp\work";

        Directory.Delete(folderPath, recursive: true);

        Console.WriteLine("ディレクトリとその中身をすべて削除しました。");
    }
}
C#

この recursive: true が非常に強力である一方、危険でもあります。
指定を間違えると、「想定より上の階層を消してしまった」「別のシステムが使っているフォルダまで消してしまった」といった事故につながりかねません。
実務では、このフラグを使うときこそ「パスの確認」「ログ」「制限」をしっかり設計することが大切です。


実務で使える「安全なディレクトリ削除」ユーティリティ

存在チェック+ログ出力をひとまとめにする

業務コードでは、「削除対象のフォルダがそもそも存在しない」「削除に成功したかどうかをログに残したい」といった要件がよくあります。
そのたびに Directory.ExistsDirectory.Delete を毎回書くと、コードが散らばって読みにくくなります。

そこで、「安全な削除」をユーティリティメソッドとしてまとめておくと便利です。

using System;
using System.IO;

public static class DirectoryDeleteUtil
{
    public static bool DeleteIfExists(string folderPath, bool recursive)
    {
        if (!Directory.Exists(folderPath))
        {
            Console.WriteLine($"削除対象ディレクトリが存在しません: {folderPath}");
            return false;
        }

        Directory.Delete(folderPath, recursive);
        Console.WriteLine($"ディレクトリを削除しました: {folderPath}");
        return true;
    }
}
C#

使い方の例は次の通りです。

class Program
{
    static void Main()
    {
        string folderPath = @"C:\temp\work";

        bool deleted = DirectoryDeleteUtil.DeleteIfExists(folderPath, recursive: true);

        if (deleted)
        {
            Console.WriteLine("削除済みとして後続処理を進めます。");
        }
        else
        {
            Console.WriteLine("そもそも存在しなかったので、削除は行われていません。");
        }
    }
}
C#

ここで深掘りしたいポイントは、「削除したかどうかを戻り値で返している」ことと、「存在しない場合は例外ではなく情報として扱っている」ことです。
業務によっては、「存在しない=問題なし」とみなすほうが自然な場合が多く、その考え方をコードに反映しています。

ルート制限をかけて「消してよい場所」を限定する

ディレクトリ削除で一番怖いのは、「消してはいけない場所を指定してしまう」ことです。
たとえば、設定ミスで C:\temp\work のつもりが C:\temp になっていた、などです。

実務では、「このルート配下だけ削除を許可する」という制限をユーティリティに組み込むことがあります。

using System;
using System.IO;

public static class DirectoryDeleteUtil
{
    public static bool DeleteIfExistsUnderRoot(string folderPath, string allowedRoot, bool recursive)
    {
        string fullTarget = Path.GetFullPath(folderPath);
        string fullRoot   = Path.GetFullPath(allowedRoot);

        if (!fullTarget.StartsWith(fullRoot, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException(
                $"許可されていない場所の削除が要求されました: {fullTarget} / ルート: {fullRoot}"
            );
        }

        if (!Directory.Exists(fullTarget))
        {
            Console.WriteLine($"削除対象ディレクトリが存在しません: {fullTarget}");
            return false;
        }

        Directory.Delete(fullTarget, recursive);
        Console.WriteLine($"ディレクトリを削除しました: {fullTarget}");
        return true;
    }
}
C#

使い方の例は次の通りです。

class Program
{
    static void Main()
    {
        string root       = @"C:\app\work";
        string targetPath = @"C:\app\work\temp1";

        DirectoryDeleteUtil.DeleteIfExistsUnderRoot(targetPath, root, recursive: true);
    }
}
C#

ここでの重要ポイントは、「削除してよい範囲をコードで明示している」ことです。
これにより、設定ミスやバグで「アプリの作業領域の外」を消してしまうリスクを大きく減らせます。


一時ディレクトリや作業領域のクリーンアップ

処理ごとに作る作業フォルダを丸ごと消す

バッチ処理やファイル変換処理では、「処理ごとに一時フォルダを作り、その中で作業して、最後に丸ごと消す」というパターンがよくあります。
このとき、ディレクトリ作成と削除をセットでユーティリティ化しておくと、コードが分かりやすくなります。

using System;
using System.IO;

public static class WorkDirectoryUtil
{
    public static string CreateWorkDirectory(string baseFolder)
    {
        Directory.CreateDirectory(baseFolder);

        string workFolder = Path.Combine(
            baseFolder,
            "work_" + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff")
        );

        Directory.CreateDirectory(workFolder);
        Console.WriteLine($"作業ディレクトリを作成しました: {workFolder}");
        return workFolder;
    }

    public static void CleanupWorkDirectory(string workFolder)
    {
        if (!Directory.Exists(workFolder))
        {
            Console.WriteLine($"作業ディレクトリが既に存在しません: {workFolder}");
            return;
        }

        Directory.Delete(workFolder, recursive: true);
        Console.WriteLine($"作業ディレクトリを削除しました: {workFolder}");
    }
}
C#

使い方のイメージは次の通りです。

class Program
{
    static void Main()
    {
        string baseFolder = @"C:\app\work";

        string workFolder = WorkDirectoryUtil.CreateWorkDirectory(baseFolder);

        try
        {
            Console.WriteLine("ここで作業フォルダ内で処理を行うイメージです。");
        }
        finally
        {
            WorkDirectoryUtil.CleanupWorkDirectory(workFolder);
        }
    }
}
C#

ここで深掘りしたいのは、「作業フォルダのライフサイクルをコードで明確にしている」ことです。
作る場所、名前のルール、削除のタイミングをユーティリティに閉じ込めることで、業務コード側は「作る」「使う」「片付ける」という流れに集中できます。


例外とエラー処理を意識したディレクトリ削除

どんな例外が起こり得るか

ディレクトリ削除では、次のような理由で例外が発生する可能性があります。
権限がなくて削除できない、中のファイルが別プロセスにロックされている、読み取り専用属性が付いている、パスが不正、ディレクトリではなくファイルだった、などです。

例外を意識した書き方の例を見てみましょう。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string folderPath = @"C:\temp\work";

        try
        {
            Directory.Delete(folderPath, recursive: true);
            Console.WriteLine("ディレクトリ削除に成功しました。");
        }
        catch (UnauthorizedAccessException ex)
        {
            Console.WriteLine("権限エラーが発生しました: " + ex.Message);
        }
        catch (IOException ex)
        {
            Console.WriteLine("入出力エラーが発生しました: " + ex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("想定外のエラーが発生しました: " + ex.Message);
        }
    }
}
C#

実務では、これらのメッセージをログに残しておくことで、「どのディレクトリの削除に、どんな理由で失敗したか」を後から追跡できます。
特に権限やロックの問題は、アプリ側だけでは解決できないことも多いため、運用担当者が原因を判断できる情報を残しておくことが重要です。


「消しすぎない」ための考え方と設計のコツ

パスを文字列連結で作らない

削除系の処理では、「パスの組み立てミス」が致命傷になりやすいです。
たとえば、baseFolder + "\\" + subFolder のつもりが、baseFolder の末尾に \ が付いていて C:\app\work\\temp になったり、逆に \ を付け忘れて C:\app\worktemp になったりします。

C# では Path.Combine を使うことで、OS に合わせて安全にパスを組み立てられます。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string baseFolder = @"C:\app\work";
        string subFolder  = "temp";

        string targetFolder = Path.Combine(baseFolder, subFolder);

        Console.WriteLine("削除対象パス: " + targetFolder);

        DirectoryDeleteUtil.DeleteIfExists(targetFolder, recursive: true);
    }
}
C#

削除のような危険度の高い操作では、「パスの組み立てを安全に行う」ことを徹底するだけでも、事故の確率をかなり下げられます。

「どこまで消すか」をコードで固定しておく

実務では、「このフォルダ配下は自由に消してよいが、それより上は絶対に消さない」というルールを決めておくと安全です。
先ほどのルート制限ユーティリティのように、「許可されたルート配下かどうか」をコードでチェックするのは、そのルールをプログラムに落とし込んだ形です。

さらに一歩進めるなら、「削除対象のパスをログに必ず残す」「本番環境では特定のフォルダしか削除しないように設定で制限する」といった工夫も有効です。
削除は「やりすぎたら終わり」なので、「やりすぎないための仕組み」を先に作っておく、という発想がとても大事です。


まとめ 実務で使えるディレクトリ削除の考え方

ディレクトリ削除は、「中身ごと消える」操作であるがゆえに、ファイル削除以上に慎重な設計が求められます。
だからこそ、「ただ消す」ではなく、「どこを・どこまで・どんなルールで消すか」を明確にし、それをユーティリティとログで支えることが大切です。

Directory.Delete の基本的な挙動(空でないと削除できない形と、recursive: true で中身ごと削除する形)を正しく理解すること。
存在チェックやログ出力、ルート制限を組み合わせた「安全な削除」ユーティリティを用意し、誤削除のリスクを減らすこと。
一時ディレクトリや作業領域のライフサイクルを「作る→使う→丸ごと消す」としてユーティリティ化し、業務コードをシンプルに保つこと。
例外やエラーの原因をある程度分類し、運用側が原因を判断できるようなメッセージやログを残すこと。
パスの組み立てには Path.Combine を使い、「消すつもりのなかった場所」を指定してしまうリスクを減らすこと。

ここまで押さえておけば、「バックアップフォルダを丸ごと消してしまった」「作業フォルダが掃除されずにディスクがいっぱいになった」といった、現場でよくあるトラブルをかなり減らせます。

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