はじめに なぜ「ファイル削除」は慎重さが必要なのか
業務システムでは、古いログを消す、一時ファイルを片付ける、処理に失敗した中間ファイルを削除するなど、「ファイルを消す」処理が必ず出てきます。
ただし、コピーや移動と違って、削除は「基本的に元に戻せない」操作です。
設計や実装を間違えると、「必要なファイルまで消してしまった」「消した場所が分からない」といった重大事故につながります。
C# では System.IO.File.Delete を使ってファイル削除を行いますが、その前後の考え方やチェックがとても大事です。
ここでは、プログラミング初心者向けに、基本から実務で使えるユーティリティ化まで、丁寧にかみ砕いて解説していきます。
基本のメソッド File.Delete を理解する
File.Delete の最もシンプルな使い方
まずは、File.Delete の一番シンプルな例を見てみましょう。
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:\temp\work.txt";
File.Delete(path);
Console.WriteLine("ファイル削除を実行しました。");
}
}
C#このコードは、「C:\temp\work.txt を削除する」という意味です。
ここで初心者が驚きやすいポイントがひとつあります。
それは、「指定したファイルが存在しない場合でも、File.Delete は例外を投げず、何もせずに終わる」という挙動です。
つまり、「存在しない=エラー」ではなく、「存在していれば削除、なければ何もしない」という動きになっています。
この性質は、後で「安全に削除するユーティリティ」を作るときに役立ちます。
using System.IO を忘れない
File クラスは System.IO 名前空間に属しています。
そのため、ファイル操作を行うクラスでは、ファイルの先頭付近に次の一行を書いておくのが定番です。
using System.IO;
C#これを書き忘れると、File に赤い波線が出て「型または名前空間の名前 ‘File’ が見つかりません」といったコンパイルエラーになります。
「ファイル・ディレクトリ操作=using System.IO;」とセットで覚えてしまうと楽です。
実務で使える「安全な削除」ユーティリティ
存在チェック+ログ出力をひとまとめにする
業務コードでは、「削除しようとしたが、そもそもファイルがなかった」「削除に成功したかどうかをログに残したい」といった要件がよくあります。
そのたびに File.Exists と File.Delete を毎回書くと、コードが散らばって読みにくくなります。
そこで、「安全な削除」をユーティリティメソッドとしてまとめておくと便利です。
using System;
using System.IO;
public static class FileDeleteUtil
{
public static bool DeleteIfExists(string path)
{
if (!File.Exists(path))
{
Console.WriteLine($"削除対象ファイルが存在しません: {path}");
return false;
}
File.Delete(path);
Console.WriteLine($"ファイルを削除しました: {path}");
return true;
}
}
C#このユーティリティを使う側は、次のように書けます。
class Program
{
static void Main()
{
string path = @"C:\temp\work.txt";
bool deleted = FileDeleteUtil.DeleteIfExists(path);
if (deleted)
{
Console.WriteLine("削除済みとして後続処理を進めます。");
}
else
{
Console.WriteLine("そもそも存在しなかったので、削除は行われていません。");
}
}
}
C#ここで深掘りしたいポイントは、「削除したかどうかを戻り値で返している」ことです。
これにより、「削除できた場合だけ何かする」「存在しなかった場合は別の対応をする」といった分岐が書きやすくなります。
例外を投げるバージョンも用意しておく
「このファイルが削除できないなら処理を止めたい」というケースもあります。
たとえば、「一時ファイルが残っていると次の処理が正しく動かない」といった場面です。
using System;
using System.IO;
public static class FileDeleteUtil
{
public static void EnsureDeleted(string path)
{
if (!File.Exists(path))
{
// そもそも存在しないなら「削除済み」とみなしてよい、という設計
Console.WriteLine($"削除対象ファイルは既に存在しません: {path}");
return;
}
try
{
File.Delete(path);
Console.WriteLine($"ファイルを削除しました: {path}");
}
catch (Exception ex)
{
throw new IOException($"ファイルの削除に失敗しました: {path}", ex);
}
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string path = @"C:\temp\lock.tmp";
try
{
FileDeleteUtil.EnsureDeleted(path);
Console.WriteLine("削除が保証された状態で処理を続行します。");
}
catch (IOException ex)
{
Console.WriteLine("致命的な削除エラー: " + ex.Message);
// 実務ではここでログ出力や通知、処理中断などを行う
}
}
}
C#ここでのポイントは、「存在しない場合は成功とみなすかどうか」を設計で決めていることです。
業務によっては、「存在しない=問題なし」と扱うほうが自然な場合も多いです。
削除対象を間違えないための工夫
パスの組み立ては Path.Combine を使う
削除で一番怖いのは、「消すつもりのなかった場所を指定してしまう」ことです。
たとえば、文字列連結のミスで C:\tempwork.txt のつもりが C:\temp\work.txt になっていなかった、などです。
C# では Path.Combine を使うことで、OS に合わせて安全にパスを組み立てられます。
using System;
using System.IO;
class Program
{
static void Main()
{
string folder = @"C:\temp";
string fileName = "work.txt";
string path = Path.Combine(folder, fileName);
Console.WriteLine("削除対象パス: " + path);
FileDeleteUtil.DeleteIfExists(path);
}
}
C#Path.Combine を使うことで、「区切り文字 \ の重複や抜け」を気にせずに済みます。
削除のような危険度の高い操作では、「パスの組み立てを安全に行う」ことが特に重要です。
ログに「どのファイルを消したか」を必ず残す
業務システムでは、「いつ・どのファイルを削除したか」を後から追えるようにしておくことが大切です。
コンソール出力だけでなく、ログファイルや監視システムに記録するのが一般的です。
たとえば、次のようなイメージです。
public static class FileDeleteUtil
{
public static bool DeleteIfExistsWithLog(string path, Action<string> log)
{
if (!File.Exists(path))
{
log($"[INFO] 削除対象ファイルが存在しません: {path}");
return false;
}
File.Delete(path);
log($"[INFO] ファイルを削除しました: {path}");
return true;
}
}
C#ここでは、ログの出力方法(ファイルに書くのか、コンソールに出すのか、外部サービスに送るのか)を Action<string> で外から渡せるようにしています。
こうしておくと、ユーティリティ自体は「削除とメッセージ生成」に集中でき、ログの行き先は呼び出し側で自由に変えられます。
古いファイルをまとめて削除するユーティリティ
「何日より古いファイルを消す」は超・実務的
ログフォルダやバックアップフォルダは、放っておくとどんどん容量を食いつぶします。
そこでよく出てくる要件が、「〇日より古いファイルを自動で削除したい」です。
C# では Directory.GetFiles と File.GetLastWriteTime を組み合わせることで、こうした「期限付き削除」ユーティリティを作れます。
using System;
using System.IO;
public static class FileCleanupUtil
{
public static int DeleteOldFiles(string folderPath, int days, Action<string>? log = null)
{
if (!Directory.Exists(folderPath))
{
log?.Invoke($"[WARN] 対象フォルダが存在しません: {folderPath}");
return 0;
}
DateTime threshold = DateTime.Now.AddDays(-days);
int deleteCount = 0;
foreach (string filePath in Directory.GetFiles(folderPath))
{
DateTime lastWrite = File.GetLastWriteTime(filePath);
if (lastWrite < threshold)
{
File.Delete(filePath);
deleteCount++;
log?.Invoke?.Invoke($"[INFO] 古いファイルを削除しました: {filePath} (最終更新: {lastWrite})");
}
}
log?.Invoke($"[INFO] 削除件数: {deleteCount} 件");
return deleteCount;
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string logFolder = @"C:\logs\app";
int deleted = FileCleanupUtil.DeleteOldFiles(
logFolder,
days: 30,
log: message => Console.WriteLine(message)
);
Console.WriteLine($"30日より古いログファイルを {deleted} 件削除しました。");
}
}
C#ここで深掘りしたいポイントは、「削除対象の条件をコードで明確に表現している」ことです。
「最終更新日時が〇日より前」というルールを、DateTime の比較で正確に書いているので、運用ルールとコードの対応関係が分かりやすくなります。
例外とエラー処理を意識した削除
どんな例外が起こり得るか
ファイル削除では、次のような理由で例外が発生する可能性があります。
ファイルが別プロセスにロックされている、権限がない、読み取り専用属性が付いている、パスが不正、ディレクトリをファイルとして削除しようとしている、などです。
例外をある程度分類して扱う例を見てみましょう。
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:\temp\work.txt";
try
{
File.Delete(path);
Console.WriteLine("ファイル削除に成功しました。");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("権限エラーが発生しました: " + ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("入出力エラーが発生しました: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("想定外のエラーが発生しました: " + ex.Message);
}
}
}
C#実務では、これらのメッセージをログに残しておくことで、「どのファイルの削除に、どんな理由で失敗したか」を後から追跡できます。
特に権限やロックの問題は、アプリ側だけでは解決できないことも多いため、運用担当者が原因を判断できる情報を残しておくことが重要です。
まとめ 実務で使えるファイル削除の考え方
ファイル削除は、「元に戻せない」操作であるがゆえに、コピーや移動以上に慎重な設計が求められます。
だからこそ、「ただ消す」ではなく、「何を・いつ・どんなルールで消すか」を明確にし、ログやユーティリティでそれを支えることが大切です。
File.Delete の基本的な挙動(存在しない場合は例外を投げず、何もしない)を正しく理解すること。
存在チェックやログ出力を組み合わせた「安全な削除」ユーティリティを用意し、削除の成否を戻り値やログで確認できるようにすること。
パスの組み立てには Path.Combine を使い、「消すつもりのなかった場所」を指定してしまうリスクを減らすこと。
古いファイルをまとめて削除するユーティリティを用意し、ログやバックアップの容量を自動でコントロールできるようにすること。
例外やエラーの原因をある程度分類し、運用側が原因を判断できるようなメッセージやログを残すこと。
ここまで押さえておけば、「必要なファイルを誤って削除した」「削除に失敗していたのに誰も気づかなかった」といった、現場でよくあるトラブルをかなり減らせます。
