CodeGym /コース /C# SELF /ファイルとディレクトリのコピー

ファイルとディレクトリのコピー

C# SELF
レベル 40 , レッスン 2
使用可能

1. ファイルのコピー

これまではファイルシステムの操作は「単発の一撃」のような感じでした: ファイルを作って、読んで、削除して—それで終わり。でも現実には内容をコピーする必要がよく出てきます: ドキュメントのバックアップ、テンプレートの複製、データ処理の自動化など。コピーは子供でもできそうに見えます(Ctrl+C と Ctrl+V が勝負!)が、実際にはいくつかのニュアンスがあります。

今日はファイルやフォルダをコピーする全ての方法を見ていきます—一番シンプルなものから少し高度なものまで。組み込みの .NET クラスがどう解決するか、どんな制限やエラーがあるか、ディレクトリが二千個、ファイルが百万個あったらどうするかも確認します。

クラス File とメソッド Copy

ファイルをコピーする最も簡単な方法は静的メソッド File.Copy を使うことです。このメソッドはソースファイルのパスと新しいファイルのパス、そしてオプションのパラメータ(既に存在する場合に上書きするかどうか)を受け取ります。

using System.IO;

// シンプルなファイルコピーの例
File.Copy("source.txt", "destination.txt");

もし宛先ファイルが既に存在していると、メソッドは例外を投げます。上書きを明示的に許可するには第三引数を使います:

File.Copy("source.txt", "destination.txt", overwrite: true);

重要なポイント: 第二引数("destination.txt")がファイルではなく既存のディレクトリへのパスだった場合、エラーになります。メソッドはファイルのパスを期待しています!

パスの扱い

前と同様に、二重スラッシュや逆スラッシュの罠に引っかからないように Path.Combine を使うのを忘れないでください:

string sourcePath = Path.Combine("Data", "input.txt");
string destPath = Path.Combine("Backup", "input_backup.txt");

File.Copy(sourcePath, destPath, overwrite: true);

エラー処理

ファイルをコピーする際に何がうまくいかないか?いろいろあります: ファイルが別プロセスにロックされている、ソースが存在しない、権限がない、宛先ディスクが満杯など。例外処理を使いましょう:

try
{
    File.Copy("bigdata.txt", "bigdata_backup.txt", overwrite: false);
    Console.WriteLine("ファイルのコピーに成功しました!");
}
catch (IOException ex)
{
    Console.WriteLine($"入出力エラー: {ex.Message}");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("ファイルまたはフォルダへのアクセスがありません。");
}
catch (Exception ex)
{
    Console.WriteLine($"その他のエラー: {ex.Message}");
}

クラス FileInfo とメソッド CopyTo

既に FileInfo オブジェクトを持っているなら、その上で CopyTo を呼べます:

var fi = new FileInfo("report.xlsx");
fi.CopyTo("backup_report.xlsx");

第三引数(overwrite)は CopyTo に .NET Core 2.0+ で追加されました。もし古いフレームワーク向けに書いているなら、エラーに驚かないでください。

「どこへも行かない」ファイルコピー(または「捕まらなければ泥棒じゃない」)

コピー先のパスに注意してください。宛先ディレクトリが存在しないと .NET はエラーを出します。だからファイルをコピーする前に受け取り側ディレクトリが存在するか確認するのが良いです:

string backupDir = "Backup";
if (!Directory.Exists(backupDir))
{
    Directory.CreateDirectory(backupDir);
}

string targetPath = Path.Combine(backupDir, "mydoc.txt");
File.Copy("mydoc.txt", targetPath);

2. ディレクトリのコピー: 心臓の強さが問われる作業

ここから本当の冒険が始まります! .NET に Directory.Copy のような「一行で全部やってくれる」魔法のメソッドはありません(File クラスのように)。手を動かして、すべてのファイルとサブフォルダを再帰的にコピーする関数を書く必要があります。

なぜ Directory.Copy は存在しないのか?

ディレクトリのコピーは常に単純ではありません。各フォルダにはファイル、サブフォルダ、隠しファイル、特殊な権限や長いパスが含まれる可能性があります。だから .NET の設計者は「何をコピーするかはプログラマが決めてね」としたわけです。けれど僕らは楽な道を探すわけじゃない—自分で関数を書きましょう!

ディレクトリ再帰コピーの例

課題: あるフォルダ(とその全サブワールド)の内容を別のフォルダにコピーする。

using System;
using System.IO;

void CopyDirectory(string sourceDir, string destDir, bool recursive)
{
    // ソースフォルダが存在するかチェック
    if (!Directory.Exists(sourceDir))
        throw new DirectoryNotFoundException($"ソースフォルダが見つかりません: {sourceDir}");

    // 宛先フォルダを作成(まだなければ)
    if (!Directory.Exists(destDir))
        Directory.CreateDirectory(destDir);

    // すべてのファイルをコピー
    foreach (string filePath in Directory.GetFiles(sourceDir))
    {
        string fileName = Path.GetFileName(filePath);
        string destFilePath = Path.Combine(destDir, fileName);
        File.Copy(filePath, destFilePath, overwrite: true);
    }

    // 再帰が有効ならサブフォルダもコピー
    if (recursive)
    {
        foreach (string dirPath in Directory.GetDirectories(sourceDir))
        {
            string dirName = Path.GetFileName(dirPath);
            string destSubDir = Path.Combine(destDir, dirName);
            // 再帰呼び出し!
            CopyDirectory(dirPath, destSubDir, recursive);
        }
    }
}

使い方:

CopyDirectory("C:\\MyData", "D:\\Backup\\MyData", recursive: true);

この関数はディレクトリ構造を作成し、サブフォルダの内容も含めて全てのファイルをコピーします。

コードと注意点の解析

まず宛先ディレクトリを作成します(なければ)—さもないとファイルをコピーしようとしたときにエラーになります。ループ内では Path.GetFileName を使って、ファイル名やフォルダ名を新しいパスに失わないようにしています。

ちなみに、ディレクトリを自分自身またはそのサブフォルダにコピーしようとすると壮大な再帰…つまり StackOverflowException を食らいます。"C:\\Data""C:\\Data\\Backup" にコピーするのはやめましょう。PC が怒ります!

ファイルのみをコピーする(サブフォルダなし)

ときどき、トップレベルのファイルだけをコピーすれば十分な場合があります(サブフォルダには潜らない):

void CopyFilesOnly(string sourceDir, string destDir)
{
    if (!Directory.Exists(destDir))
        Directory.CreateDirectory(destDir);

    foreach (string filePath in Directory.GetFiles(sourceDir))
    {
        string fileName = Path.GetFileName(filePath);
        string destFilePath = Path.Combine(destDir, fileName);
        File.Copy(filePath, destFilePath, overwrite: true);
    }
}

例 — バックアップ機能を実装する

この機能をコース全体で育てている「HomeApp」に追加しましょう。アプリが自分のデータのバックアップを作れるようにします。

using System;
using System.IO;

namespace HomeApp
{
    class Program
    {
        static void CopyDirectory(string sourceDir, string destDir, bool recursive)
        {
            if (!Directory.Exists(sourceDir))
                throw new DirectoryNotFoundException($"ソースフォルダが見つかりません: {sourceDir}");

            if (!Directory.Exists(destDir))
                Directory.CreateDirectory(destDir);

            foreach (string filePath in Directory.GetFiles(sourceDir))
            {
                string fileName = Path.GetFileName(filePath);
                string destFilePath = Path.Combine(destDir, fileName);
                File.Copy(filePath, destFilePath, overwrite: true);
            }

            if (recursive)
            {
                foreach (string dirPath in Directory.GetDirectories(sourceDir))
                {
                    string dirName = Path.GetFileName(dirPath);
                    string destSubDir = Path.Combine(destDir, dirName);
                    CopyDirectory(dirPath, destSubDir, recursive);
                }
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("作業ディレクトリのパスを入力してください:");
            string source = Console.ReadLine()!;
            Console.WriteLine("バックアップ先フォルダのパスを入力してください:");
            string dest = Console.ReadLine()!;

            try
            {
                CopyDirectory(source, dest, recursive: true);
                Console.WriteLine("バックアップが正常に作成されました!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("コピー中にエラーが発生しました: " + ex.Message);
            }
        }
    }
}

3. 便利な注意点

比較: ファイル vs ディレクトリ — 表

ファイル ディレクトリ
組み込みメソッド
File.Copy
-
オブジェクト指向
FileInfo.CopyTo
-
ネストの巡回 不要 再帰が必要
構造の作成 自動的 手動でネストを作成する必要がある
再帰の危険性 なし あり — 「自分自身」をコピーしないこと

実践的な用途と練習課題

ファイルやフォルダのコピーは、バックアップからデータ移行、ゲームランチャーのリソース更新まで幅広く使われます。こうした処理は手作業を避けるために自動化されるのが普通で、月曜朝に「あ、ファイル忘れた!」を防げます。

ディレクトリのコピーはコンテンツだけでなく構造(サブフォルダやネストされたファイル、設定)も保持する必要がある場面で特に重要です。

コピー時のチェックリスト(失敗を避けるために)

  • ソースの存在確認
  • 宛先フォルダの存在確認(必要なら Directory.CreateDirectory で作成)
  • 上書きの方針 — overwrite: true が必要か?
  • フォルダを「自分自身」にコピーしていないか?
  • パス長が制限を超えていないか(特に Windows)
  • 隠し/システムファイルを考慮しているか?

4. コピーの特徴と典型的なエラー

アクセス権

プログラムにソースの読み取り権や宛先への書き込み権がないとコピーは失敗します。その場合は UnauthorizedAccessException の一種が発生します。解決策は管理者として実行する(本当に必要な場合のみ)か、普通のフォルダを使うことです。

ファイルの使用中

ファイルが別のアプリケーション(例: Excel)が開いてロックされていると、File.Copy は例外を投げます。ファイルをロックしているアプリが邪魔していないか確認するか、リトライロジック(try-catch と再試行)を実装しましょう。

ファイルの上書き

どのロジックを採るか: 宛先ファイルを上書きするか、既存なら無視するか?バックアップなら通常は上書き(overwrite: true)、テンプレート複製なら上書きしない(overwrite: false)のが適切です。

隠し・システムファイルのコピー

ここまで使ったメソッド(Directory.GetFiles など)はデフォルトで隠しファイルやシステムファイルも返します。スキップしたい場合は明示的にフィルタリングしてください:

foreach (string filePath in Directory.GetFiles(sourceDir))
{
    var attr = File.GetAttributes(filePath);
    if ((attr & FileAttributes.Hidden) == FileAttributes.Hidden)
        continue; // 隠しファイルをスキップ

    // 他のコピー処理
}

長いパスのエラー

Windows は長らくパス長を約260文字で制限してきました。最新のバージョンではこの制限は解除できることがありますが、古いシステムを扱う場合は長いパスが問題になる可能性があります。

シンボリックリンクとジャンクション

特定のシナリオではディレクトリ内にシンボリックリンクやジャンクションが存在することがあります。通常のコピー方法はそれらを普通のフォルダとしてコピーしたり無視したりします。学習用の多くのケースでは気にしなくていいですが、システム系のフォルダを扱うときは注意してください。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION