【C#】ファイルダウンロード時の連番のファイル名を作成する

よくブラウザでファイルをダウンロードしたときに既にそのファイルがあった場合、末尾に(1)、(2)...という風に連番が付いた別のファイルが作成されます。

// こんな風に連番が自動的に作成される
Sample.zip
Sample (1).zip
Sample (2).zip

これを C# で同じような規則になるように実装してみようと思います。

作成環境

  • .NET6
  • VisualStudio2022
  • Windows11

実装コード

実装は以下の通りです。これであるフォルダの中にあるファイルを指定すると連番が付いたファイル名が作成されます。

using System;
using System.IO;
using System.Text.RegularExpressions;

public class DuplicateFileName
{
    /// <summary>
    /// ディレクトリ中で一意のファイルパスを提案します。
    /// </summary>
    /// <remarks>
    /// ブラウザでよくあるある以下のような名前の規則
    /// file-name.zip
    /// file-name (1).zip
    /// file-name (2).zip
    /// </remarks>
    public string SuggestionUniquePath(string path)
    {
        // あらかじめ構成要素を全部分解しておく
        string parent = Path.GetDirectoryName(path);
        string name = Path.GetFileNameWithoutExtension(path);
        string ext = Path.GetExtension(path);

        // 新しい名前
        string newName = name;

        string pats = GetParts(name);
        for (uint i = 0; i < uint.MaxValue - 1; i++)
        {
            newName = $"{pats} ({i + 1})";
            string newPath = Path.Combine(parent, $"{newName}{ext}");
            if (!File.Exists(newPath))
            {
                return Path.Combine(parent, $"{newName}{ext}");
            }
        }
        throw new NotSupportedException("そんなばなな!");
    }

    private static string GetParts(string name)
    {
        Match m = Regex.Match(name, @"^(?<group>.*) \(\d\)$");
        return m.Success ? m.Groups["group"].Value : name;
    }
}

Sample (1)(1).zip とかにならないように正規表現で Sample という先頭の部分を取得して後ろにカッコつきの連番を付けてファイルチェックを繰り返します。

連番に穴が開いていて既に (2) があって (1) が無い場合、(1) が提案されます。

連番の最大値が、42億9496万7295なので Sample (4294967295).zip が最大です。ですが 1 つのフォルダ内に42億個もファイルが入ってる事なんか通常ありえないのでそれ以上は想定していません。もしもっとある場合 ulong (18京4467兆個)に変更します。まぁ大丈夫でしょう。

使い方

使い方は以下の通り。

private static void Main(string[] args)
{
    string path = @"D:\test.zip";

    // ファイルパス or ファイル名を指定する
    DuplicateFileName d = new();
    string newName = d.SuggestionUniquePath(path);

    Console.WriteLine(newName);
    // D:\test (1).zip
    // すでにファイルがある場合以下のように別の名前が提案される
    // D:\test (2).zip
}