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

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

はじめに なぜ「ファイル削除」は慎重さが必要なのか

業務システムでは、古いログを消す、一時ファイルを片付ける、処理に失敗した中間ファイルを削除するなど、「ファイルを消す」処理が必ず出てきます。
ただし、コピーや移動と違って、削除は「基本的に元に戻せない」操作です。
設計や実装を間違えると、「必要なファイルまで消してしまった」「消した場所が分からない」といった重大事故につながります。

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.ExistsFile.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.GetFilesFile.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 を使い、「消すつもりのなかった場所」を指定してしまうリスクを減らすこと。
古いファイルをまとめて削除するユーティリティを用意し、ログやバックアップの容量を自動でコントロールできるようにすること。
例外やエラーの原因をある程度分類し、運用側が原因を判断できるようなメッセージやログを残すこと。

ここまで押さえておけば、「必要なファイルを誤って削除した」「削除に失敗していたのに誰も気づかなかった」といった、現場でよくあるトラブルをかなり減らせます。

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