はじめに なぜ「再帰的フォルダ削除」は危険で、だからこそ重要なのか
業務システムでは、「一時作業フォルダを丸ごと消す」「古いバックアップフォルダをまとめて削除する」「処理失敗時に作業領域をクリーンアップする」といった、「フォルダごと中身を全部消す」場面が必ず出てきます。
このときに使うのが「再帰的フォルダ削除」です。
ただし、再帰的フォルダ削除は、正しく設計しないと非常に危険です。
一つパスを間違えただけで、「必要なデータを丸ごと削除してしまった」「アプリケーション本体のフォルダを消してしまった」といった致命的な事故につながります。
だからこそ、「どう書くか」だけでなく、「どう制限するか」「どうログを残すか」まで含めて設計することが、実務ではとても大切になります。
ここでは、C# の Directory.Delete をベースに、「再帰的フォルダ削除」を初心者向けにかみ砕きつつ、業務でそのまま使えるユーティリティの形まで落とし込んでいきます。
基本のメソッド Directory.Delete と recursive 引数
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: true で中身ごと削除する
フォルダの中身も含めて丸ごと削除したいときは、Directory.Delete の第二引数 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 を指定すると、そのフォルダ内のファイル、サブフォルダ、そのまた中のファイル…というように、階層をたどりながらすべて削除してくれます。
つまり、「再帰的フォルダ削除」を、.NET が内部でやってくれているイメージです。
ここで深掘りしたいのは、「この一行が非常に強力であると同時に、とても危険でもある」ということです。
パスを一文字間違えただけで、「消すつもりのなかったフォルダ」を丸ごと削除してしまう可能性があります。
再帰的フォルダ削除をユーティリティとして包む
存在チェックとログを組み合わせた基本ユーティリティ
業務コードのあちこちで、いきなり Directory.Delete(path, true); と書くのは危険です。
そこで、「存在チェック」「ログ出力」を含めたユーティリティメソッドとして包んでおくと、安全性と読みやすさがぐっと上がります。
using System;
using System.IO;
public static class RecursiveDirectoryDeleteUtil
{
public static bool DeleteDirectoryIfExists(string folderPath)
{
if (!Directory.Exists(folderPath))
{
Console.WriteLine($"削除対象ディレクトリが存在しません: {folderPath}");
return false;
}
Directory.Delete(folderPath, recursive: true);
Console.WriteLine($"ディレクトリを再帰的に削除しました: {folderPath}");
return true;
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string folderPath = @"C:\temp\work";
bool deleted = RecursiveDirectoryDeleteUtil.DeleteDirectoryIfExists(folderPath);
if (deleted)
{
Console.WriteLine("削除済みとして後続処理を進めます。");
}
else
{
Console.WriteLine("そもそも存在しなかったので、削除は行われていません。");
}
}
}
C#ここでのポイントは、「存在しない場合は例外ではなく情報として扱い、戻り値で削除の有無を返している」ことです。
業務によっては、「存在しない=問題なし」とみなすほうが自然な場合が多く、その考え方をコードに反映しています。
ログ出力先を差し替えられるようにする
実務では、コンソールではなく、ファイルログや監視システムにメッセージを送りたいことが多いです。
そこで、ログ出力をデリゲートとして受け取る形にしておくと、再利用性が高くなります。
using System;
using System.IO;
public static class RecursiveDirectoryDeleteUtil
{
public static bool DeleteDirectoryIfExists(
string folderPath,
Action<string>? log = null)
{
if (!Directory.Exists(folderPath))
{
log?.Invoke($"[INFO] 削除対象ディレクトリが存在しません: {folderPath}");
return false;
}
Directory.Delete(folderPath, recursive: true);
log?.Invoke($"[INFO] ディレクトリを再帰的に削除しました: {folderPath}");
return true;
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string folderPath = @"C:\temp\work";
RecursiveDirectoryDeleteUtil.DeleteDirectoryIfExists(
folderPath,
message => Console.WriteLine(message)
);
}
}
C#こうしておくと、将来「ログをファイルに書きたい」「外部のロガーライブラリを使いたい」となったときも、ユーティリティ本体を変えずに対応できます。
「消してよい範囲」を制限するルートチェック
一番怖いのは「消しすぎる」こと
再帰的フォルダ削除で一番怖いのは、「消すつもりのなかった場所まで消してしまう」ことです。
たとえば、本当は C:\app\work\temp を消したかったのに、設定ミスで C:\app を渡してしまった、というようなケースです。
このリスクを減らすために、「このルート配下だけ削除を許可する」という制限をユーティリティに組み込むのが、実務的な防御策になります。
ルート配下かどうかをチェックするユーティリティ
次のようなユーティリティを考えてみます。
using System;
using System.IO;
public static class SafeRecursiveDirectoryDeleteUtil
{
public static bool DeleteDirectoryUnderRoot(
string targetFolder,
string allowedRoot,
Action<string>? log = null)
{
string fullTarget = Path.GetFullPath(targetFolder);
string fullRoot = Path.GetFullPath(allowedRoot);
if (!fullTarget.StartsWith(fullRoot, StringComparison.OrdinalIgnoreCase))
{
string message =
$"[ERROR] 許可されていない場所の削除が要求されました: {fullTarget} / ルート: {fullRoot}";
log?.Invoke(message);
throw new InvalidOperationException(message);
}
if (!Directory.Exists(fullTarget))
{
log?.Invoke($"[INFO] 削除対象ディレクトリが存在しません: {fullTarget}");
return false;
}
Directory.Delete(fullTarget, recursive: true);
log?.Invoke($"[INFO] ディレクトリを再帰的に削除しました: {fullTarget}");
return true;
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string root = @"C:\app\work";
string targetFolder = @"C:\app\work\temp1";
SafeRecursiveDirectoryDeleteUtil.DeleteDirectoryUnderRoot(
targetFolder,
root,
message => Console.WriteLine(message)
);
}
}
C#ここで深掘りしたいポイントは、「削除してよい範囲をコードで明示している」ことです。Path.GetFullPath で正規化したうえで StartsWith でチェックすることで、「ルート配下かどうか」を判定しています。
これにより、設定ミスやバグで「アプリの作業領域の外」を消してしまうリスクを大きく減らせます。
一時ディレクトリ・作業領域のライフサイクル管理
「作る→使う→丸ごと消す」を一つの流れとして設計する
バッチ処理やファイル変換処理では、「処理ごとに一時フォルダを作り、その中で作業して、最後に丸ごと削除する」というパターンがよくあります。
このとき、ディレクトリ作成と再帰削除をセットでユーティリティ化しておくと、コードがとても分かりやすくなります。
using System;
using System.IO;
public static class WorkDirectoryManager
{
public static string CreateWorkDirectory(string baseFolder, Action<string>? log = null)
{
Directory.CreateDirectory(baseFolder);
string workFolder = Path.Combine(
baseFolder,
"work_" + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff")
);
Directory.CreateDirectory(workFolder);
log?.Invoke?.Invoke($"[INFO] 作業ディレクトリを作成しました: {workFolder}");
return workFolder;
}
public static void CleanupWorkDirectory(string workFolder, Action<string>? log = null)
{
if (!Directory.Exists(workFolder))
{
log?.Invoke($"[INFO] 作業ディレクトリが既に存在しません: {workFolder}");
return;
}
Directory.Delete(workFolder, recursive: true);
log?.Invoke($"[INFO] 作業ディレクトリを再帰的に削除しました: {workFolder}");
}
}
C#使い方のイメージは次の通りです。
class Program
{
static void Main()
{
string baseFolder = @"C:\app\work";
string workFolder = WorkDirectoryManager.CreateWorkDirectory(
baseFolder,
message => Console.WriteLine(message)
);
try
{
Console.WriteLine("ここで作業フォルダ内で処理を行うイメージです。");
}
finally
{
WorkDirectoryManager.CleanupWorkDirectory(
workFolder,
message => Console.WriteLine(message)
);
}
}
}
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#実務では、これらのメッセージをファイルログや監視システムに記録しておくことで、「どのフォルダの削除に、どんな理由で失敗したか」を後から追跡できます。
特に権限やロックの問題は、アプリ側だけでは解決できないことも多いため、運用担当者が原因を判断できる情報を残しておくことが重要です。
「消しすぎない」ための設計のコツ
パスの組み立ては必ず Path.Combine を使う
削除系の処理では、「パスの組み立てミス」が致命傷になりやすいです。
文字列連結で baseFolder + "\\" + subFolder のように書くと、区切り文字の重複や抜けで、意図しないパスになることがあります。
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);
RecursiveDirectoryDeleteUtil.DeleteDirectoryIfExists(targetFolder);
}
}
C#再帰的フォルダ削除のような危険度の高い操作では、「パスの組み立てを安全に行う」ことを徹底するだけでも、事故の確率をかなり下げられます。
「どこまで消すか」をコードで固定しておく
実務では、「このフォルダ配下は自由に消してよいが、それより上は絶対に消さない」というルールを決めておくと安全です。
先ほどのルート制限ユーティリティは、そのルールをコードに落とし込んだものです。
さらに、次のような工夫も有効です。
削除対象のパスを必ずログに残す。
本番環境では、設定ファイルで「削除を許可するルート」を限定する。
テスト環境と本番環境で、ルートパスを明確に分ける。
削除は「やりすぎたら終わり」なので、「やりすぎないための仕組み」を先に作っておく、という発想がとても大事です。
まとめ 実務で使える再帰的フォルダ削除の考え方
再帰的フォルダ削除は、「フォルダとその中身を一気に片付ける」ための強力な手段であり、業務システムでは一時領域のクリーンアップや古いデータの整理などで頻繁に登場します。
その一方で、「一歩間違えると取り返しがつかない」という危険も常に抱えています。
Directory.Delete(path, recursive: true) が「中身ごと削除する」強力な操作であることを正しく理解すること。
存在チェックやログ出力を組み合わせたユーティリティで包み、あちこちに生の Directory.Delete を書かないようにすること。
ルート制限やパスの組み立てルールをコードに組み込み、「消してよい範囲」を明確にすること。
一時ディレクトリや作業領域のライフサイクルを「作る→使う→丸ごと消す」として設計し、再帰削除をその一部として位置づけること。
例外やエラーの原因をログに残し、運用側が原因を判断できるようにしておくこと。
ここまで押さえておけば、「作業フォルダが掃除されずにディスクがいっぱいになった」「逆に、消してはいけないフォルダを丸ごと消してしまった」といった、現場でよくあるトラブルをかなり減らせます。
