ChatGPT解决这个技术问题 Extra ChatGPT

在 C# 中复制目录的全部内容

我想在 C# 中将目录的全部内容从一个位置复制到另一个位置。

似乎没有办法在没有大量递归的情况下使用 System.IO 类来做到这一点。

如果我们添加对 Microsoft.VisualBasic 的引用,我们可以使用 VB 中的一种方法:

new Microsoft.VisualBasic.Devices.Computer().
    FileSystem.CopyDirectory( sourceFolder, outputFolder );

这似乎是一个相当丑陋的黑客。有没有更好的办法?

我想说看看下面发布的替代方案,VB 方式看起来并不那么难看。
当它是 .NET Framework 的一部分时,它怎么可能是一个 hack?停止编写代码并使用你得到的东西。
这是一个普遍的误解。 Microsft.VisualBasic 包含所有常见的 Visual Basic 过程,这些过程使 VB 中的编码变得更加容易。 Microsot.VisualBasic.Compatibility 是用于 VB6 旧版的程序集。
Microsoft.VisualBasic.Devices.Computer.FileSystem 有超过 2,000 行代码。 CopyDirectory 确保您不会将父文件夹复制到子文件夹和其他检查中。它是高度优化的,等等。选择的答案充其量是脆弱的代码。
@AMissico - 好的,那么为什么在 Microsoft.VisualBasic 而不是 System.IO 中优化和完整的代码?它不在 Mono 中的原因是因为所有被认为是“核心”的库都是 System.[something] - 所有其他库都不是。引用一个额外的 DLL 没有问题,但微软没有在 System.IO 中包含此功能是有充分理由的。

U
User1

容易得多

private static void CopyFilesRecursively(string sourcePath, string targetPath)
{
    //Now Create all of the directories
    foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
    {
        Directory.CreateDirectory(dirPath.Replace(sourcePath, targetPath));
    }

    //Copy all the files & Replaces any files with the same name
    foreach (string newPath in Directory.GetFiles(sourcePath, "*.*",SearchOption.AllDirectories))
    {
        File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true);
    }
}

这确实是一段很好的代码,但这不是可以在任何地方使用的代码。开发人员应该小心,因为 dirPath.Replace 可能会导致不必要的后果。只是对喜欢在网上进行复制和粘贴的人的警告。 @jayspired 发布的代码更安全,因为它不使用 string.Replace 但我确信它也有它的极端情况。
请注意此代码,因为如果目标目录已经存在,它将引发异常。它也不会覆盖已经存在的文件。只需在创建每个目录之前添加一个检查,并使用 File.Copy 的重载来覆盖目标文件(如果存在)。
@Xaisoft - 如果路径中有重复模式,Replace 就会出现问题,例如 "sourceDir/things/sourceDir/things" 应该变成 "destinationDir/things/sourceDir/things",但如果你使用 replace 它会变成 "destinationDir/things/destinationDir/things"
为什么是 *.* 而不是 *?你不想复制没有扩展名的文件吗?
让我们构建一些东西并将其贡献给开源 .NET Core ...:/
K
Konrad Rudolph

嗯,我想我误解了这个问题,但我会冒险。以下简单的方法有什么问题?

public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) {
    foreach (DirectoryInfo dir in source.GetDirectories())
        CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
    foreach (FileInfo file in source.GetFiles())
        file.CopyTo(Path.Combine(target.FullName, file.Name));
}

编辑由于这篇文章对于一个同样简单的问题的简单答案获得了令人印象深刻的反对票,让我添加一个解释。请在投票前阅读此内容。

首先,此代码无意替代问题中的代码。它仅用于说明目的。

Microsoft.VisualBasic.Devices.Computer.FileSystem.CopyDirectory 进行了一些附加的正确性测试(例如,源和目标是否是有效目录,源是否是目标的父级等),而这些测试在此答案中是缺失的。该代码可能也更加优化。

也就是说,代码运行良好。多年来,它已经(几乎相同)在成熟软件中使用。除了所有 IO 处理固有的变化无常(例如,如果用户在您的代码写入 USB 驱动器时手动拔出 USB 驱动器会发生什么?),没有已知问题。

特别要指出的是,这里使用递归绝对不是问题。无论在理论上(从概念上讲,这是最优雅的解决方案)还是在实践中:这段代码不会溢出堆栈。堆栈足够大,甚至可以处理深度嵌套的文件层次结构。早在堆栈空间成为问题之前,文件夹路径长度限制就开始了。

请注意,恶意用户可能能够通过使用每个一个字母的深度嵌套目录来打破这一假设。我没试过这个。但只是为了说明这一点:为了使此代码在典型计算机上溢出,目录必须嵌套数千次。这根本不是一个现实的场景。


这是头递归。如果目录嵌套得足够深,它可能会成为堆栈溢出的牺牲品。
直到最近,目录嵌套深度还受到操作系统的限制。我怀疑你会发现嵌套超过几百次的目录(如果有的话)。上面的代码可能需要更多。
我喜欢递归方法,堆栈溢出的风险在最坏的情况下是最小的。
@DTashkinov:对不起,但这似乎有点过分了。为什么明显的代码 == 否决?反之亦然。内置方法已经发布,但基思特意要求另一种方法。另外,你最后一句话是什么意思?对不起,但我根本不明白你投反对票的原因。
@AMissico:比什么更好?没有人声称它比框架中的 VB 代码更好。我们知道不是。
J
Justin R.

MSDN 复制:

using System;
using System.IO;

class CopyDir
{
    public static void Copy(string sourceDirectory, string targetDirectory)
    {
        DirectoryInfo diSource = new DirectoryInfo(sourceDirectory);
        DirectoryInfo diTarget = new DirectoryInfo(targetDirectory);

        CopyAll(diSource, diTarget);
    }

    public static void CopyAll(DirectoryInfo source, DirectoryInfo target)
    {
        Directory.CreateDirectory(target.FullName);

        // Copy each file into the new directory.
        foreach (FileInfo fi in source.GetFiles())
        {
            Console.WriteLine(@"Copying {0}\{1}", target.FullName, fi.Name);
            fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);
        }

        // Copy each subdirectory using recursion.
        foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
        {
            DirectoryInfo nextTargetSubDir =
                target.CreateSubdirectory(diSourceSubDir.Name);
            CopyAll(diSourceSubDir, nextTargetSubDir);
        }
    }

    public static void Main()
    {
        string sourceDirectory = @"c:\sourceDirectory";
        string targetDirectory = @"c:\targetDirectory";

        Copy(sourceDirectory, targetDirectory);
    }

    // Output will vary based on the contents of the source directory.
}

没有理由检查目录是否存在,只需调用 Directoty.CreateDirectory 如果目录已经存在则什么都不做。
对于那些希望处理超过 256 个字符的路径的人,您可以使用名为 ZetaLongPaths 的 Nuget 包
这个答案似乎是最有用的。通过使用 DirectoryInfo 而不是字符串,可以避免很多潜在的问题。
J
Josef

或者,如果您想走硬路线,请添加对 Microsoft.VisualBasic 项目的引用,然后使用以下内容:

Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(fromDirectory, toDirectory);

但是,使用其中一个递归函数是一种更好的方法,因为它不必加载 VB dll。


无论如何,这与我的做法并没有什么不同——你仍然需要加载 VB 的向后兼容的东西才能做到这一点。
加载 VB 程序集是否昂贵? VB 选项比 C# 版本优雅得多。
什么“VB 的向后兼容的东西”? CopyDirectory 使用外壳程序或框架。
我真希望它出现在 System.IO.Directory 上,但这比重写它好!
这是 imo 的方式,比任何其他选项都容易
J
Jim G.

尝试这个:

Process proc = new Process();
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.FileName = Path.Combine(Environment.SystemDirectory, "xcopy.exe");
proc.StartInfo.Arguments = @"C:\source C:\destination /E /I";
proc.Start();

您的 xcopy 参数可能会有所不同,但您明白了。


/E 告诉它复制所有子目录(甚至是空的)。 /I 告诉它,如果目标不存在,则创建一个具有该名称的目录。
添加双引号以确保安全。
添加 /Y 以防止提示覆盖现有文件。 stackoverflow.com/q/191209/138938
对不起,但这太可怕了。它假设目标系统是windows。它假定将来的版本在该特定路径中包含 xcopy.exe。它假定 xcopy 的参数不变。它需要将 xcopy 的参数组装为字符串,这会引入大量的错误可能性。此外,该示例未提及对已启动过程的结果进行任何错误处理,这是我所期望的,因为与其他方法相反,这将静默失败。
@MatthiasJansen,我认为您认为这非常个人化。答案是重点,并解释了如何实现它......由于问题不要求跨平台兼容性或不使用 xcopy 或其他任何东西,因此发布者刚刚回答解释如何以一种方式实现它......那里可能有 1000 种方法来做同样的事情,答案各不相同。这就是为什么这个论坛在这里讨论,全球的程序员来到这里分享他们的经验。我对你的评论投了反对票。
e
eduardomozart

这个网站总是帮了我很多,现在轮到我用我所知道的来帮助其他人了。

我希望我下面的代码对某人有用。

string source_dir = @"E:\";
string destination_dir = @"C:\";

// substring is to remove destination_dir absolute path (E:\).

// Create subdirectory structure in destination    
    foreach (string dir in System.IO.Directory.GetDirectories(source_dir, "*", System.IO.SearchOption.AllDirectories))
    {
        System.IO.Directory.CreateDirectory(System.IO.Path.Combine(destination_dir, dir.Substring(source_dir.Length + 1)));
        // Example:
        //     > C:\sources (and not C:\E:\sources)
    }

    foreach (string file_name in System.IO.Directory.GetFiles(source_dir, "*", System.IO.SearchOption.AllDirectories))
    {
        System.IO.File.Copy(file_name, System.IO.Path.Combine(destination_dir, file_name.Substring(source_dir.Length + 1)));
    }

记住尾随反斜杠
伙计们,使用Path.Combine()。切勿使用字符串连接将文件路径放在一起。
您在上面的代码片段中有一个 OBOB。您应该使用 source_dir.Length + 1,而不是 source_dir.Length
这段代码是一个很好的概念,但是……文件不一定要有“。”在其中,所以最好使用 ystem.IO.Directory.GetFiles(source_dir, "*", System.IO.SearchOption.AllDirectories))
谢谢@JeanLibera,你是对的。我根据您的建议更改了代码。
s
sarnold

递归复制文件夹而不递归以避免堆栈溢出。

public static void CopyDirectory(string source, string target)
{
    var stack = new Stack<Folders>();
    stack.Push(new Folders(source, target));

    while (stack.Count > 0)
    {
        var folders = stack.Pop();
        Directory.CreateDirectory(folders.Target);
        foreach (var file in Directory.GetFiles(folders.Source, "*.*"))
        {
            File.Copy(file, Path.Combine(folders.Target, Path.GetFileName(file)));
        }

        foreach (var folder in Directory.GetDirectories(folders.Source))
        {
            stack.Push(new Folders(folder, Path.Combine(folders.Target, Path.GetFileName(folder))));
        }
    }
}

public class Folders
{
    public string Source { get; private set; }
    public string Target { get; private set; }

    public Folders(string source, string target)
    {
        Source = source;
        Target = target;
    }
}

有用的非递归模板:)
很难想象在发光路径限制之前炸毁堆栈
佚名

这是我用于此类 IO 任务的实用程序类。

using System;
using System.Runtime.InteropServices;

namespace MyNameSpace
{
    public class ShellFileOperation
    {
        private static String StringArrayToMultiString(String[] stringArray)
        {
            String multiString = "";

            if (stringArray == null)
                return "";

            for (int i=0 ; i<stringArray.Length ; i++)
                multiString += stringArray[i] + '\0';

            multiString += '\0';

            return multiString;
        }

        public static bool Copy(string source, string dest)
        {
            return Copy(new String[] { source }, new String[] { dest });
        }

        public static bool Copy(String[] source, String[] dest)
        {
            Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

            FileOpStruct.hwnd = IntPtr.Zero;
            FileOpStruct.wFunc = (uint)Win32.FO_COPY;

            String multiSource = StringArrayToMultiString(source);
            String multiDest = StringArrayToMultiString(dest);
            FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
            FileOpStruct.pTo = Marshal.StringToHGlobalUni(multiDest);

            FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION;
            FileOpStruct.lpszProgressTitle = "";
            FileOpStruct.fAnyOperationsAborted = 0;
            FileOpStruct.hNameMappings = IntPtr.Zero;

            int retval = Win32.SHFileOperation(ref FileOpStruct);

            if(retval != 0) return false;
            return true;
        }

        public static bool Move(string source, string dest)
        {
            return Move(new String[] { source }, new String[] { dest });
        }

        public static bool Delete(string file)
        {
            Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

            FileOpStruct.hwnd = IntPtr.Zero;
            FileOpStruct.wFunc = (uint)Win32.FO_DELETE;

            String multiSource = StringArrayToMultiString(new string[] { file });
            FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
            FileOpStruct.pTo =  IntPtr.Zero;

            FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_SILENT | (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION | (ushort)Win32.ShellFileOperationFlags.FOF_NOERRORUI | (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMMKDIR;
            FileOpStruct.lpszProgressTitle = "";
            FileOpStruct.fAnyOperationsAborted = 0;
            FileOpStruct.hNameMappings = IntPtr.Zero;

            int retval = Win32.SHFileOperation(ref FileOpStruct);

            if(retval != 0) return false;
            return true;
        }

        public static bool Move(String[] source, String[] dest)
        {
            Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

            FileOpStruct.hwnd = IntPtr.Zero;
            FileOpStruct.wFunc = (uint)Win32.FO_MOVE;

            String multiSource = StringArrayToMultiString(source);
            String multiDest = StringArrayToMultiString(dest);
            FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
            FileOpStruct.pTo = Marshal.StringToHGlobalUni(multiDest);

            FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION;
            FileOpStruct.lpszProgressTitle = "";
            FileOpStruct.fAnyOperationsAborted = 0;
            FileOpStruct.hNameMappings = IntPtr.Zero;

            int retval = Win32.SHFileOperation(ref FileOpStruct);

            if(retval != 0) return false;
            return true;
        }
    }
}

请注意,Microsoft 在内部为 Microsoft.VisualBasic 使用 SHFileOperation。
b
bh_earth0

tboswell 的替换证明版本(对文件路径中的重复模式具有弹性)

public static void copyAll(string SourcePath , string DestinationPath )
{
   //Now Create all of the directories
   foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories))
      Directory.CreateDirectory(Path.Combine(DestinationPath ,dirPath.Remove(0, SourcePath.Length ))  );

   //Copy all the files & Replaces any files with the same name
   foreach (string newPath in Directory.GetFiles(SourcePath, "*.*",  SearchOption.AllDirectories))
      File.Copy(newPath, Path.Combine(DestinationPath , newPath.Remove(0, SourcePath.Length)) , true);
    }

伙计们,使用Path.Combine()。切勿使用字符串连接将文件路径放在一起。
就我而言,对于目录,我必须使用 Path.Join() 而不是 Path.Combine()。我不完全明白为什么,但我想我在 the documentation 中做了与此评论相关的事情,推荐 Path.Join()
J
Jerry Liang

我的解决方案基本上是对@Termininja 答案的修改,但是我对其进行了一些改进,它似乎比接受的答案快 5 倍以上。

public static void CopyEntireDirectory(string path, string newPath)
{
    Parallel.ForEach(Directory.GetFileSystemEntries(path, "*", SearchOption.AllDirectories)
    ,(fileName) =>
    {
        string output = Regex.Replace(fileName, "^" + Regex.Escape(path), newPath);
        if (File.Exists(fileName))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(output));
            File.Copy(fileName, output, true);
        }
        else
            Directory.CreateDirectory(output);
    });
}

编辑:将@Ahmed Sabry 修改为完全并行的 foreach 确实会产生更好的结果,但是代码使用递归函数,在某些情况下并不理想。

public static void CopyEntireDirectory(DirectoryInfo source, DirectoryInfo target, bool overwiteFiles = true)
{
    if (!source.Exists) return;
    if (!target.Exists) target.Create();

    Parallel.ForEach(source.GetDirectories(), (sourceChildDirectory) =>
        CopyEntireDirectory(sourceChildDirectory, new DirectoryInfo(Path.Combine(target.FullName, sourceChildDirectory.Name))));

    Parallel.ForEach(source.GetFiles(), sourceFile =>
        sourceFile.CopyTo(Path.Combine(target.FullName, sourceFile.Name), overwiteFiles));
}

O
Ohad Schneider

它可能不具备性能感知能力,但我将它用于 30MB 的文件夹,并且它运行良好。另外,我不喜欢这么简单的任务所需的所有代码量和递归。

var src = "c:\src";
var dest = "c:\dest";
var cmp = CompressionLevel.NoCompression;
var zip = source_folder + ".zip";

ZipFile.CreateFromDirectory(src, zip, cmp, includeBaseDirectory: false);
ZipFile.ExtractToDirectory(zip, dest_folder);

File.Delete(zip);

注意:ZipFile 在 .NET 4.5+ 的 System.IO.Compression 命名空间中可用


我也不是,因此是这个问题,但所选答案不需要递归。这个答案在磁盘上创建了一个 zip 文件,这对于文件副本来说是很多额外的工作 - 您不仅要创建数据的额外副本,而且还要花费处理器时间来压缩和解压缩它。我相信它是有效的,就像你可能会用你的鞋子敲钉子一样,但它更多的工作是处理更多可能出错的事情,同时有更好的方法来做到这一点。
我最终得到这个的原因是字符串替换。正如其他人指出的那样,公认的答案提出了许多问题。 junction 链接可能不起作用,以及重复文件夹模式或没有扩展名或名称的文件。更少的代码,更少的出错机会。而且由于处理器时间对我来说不是问题,因此它适合我的具体情况
是的,这就像驾驶 1000 英里以避开一个红绿灯,但这是你的旅程,所以去吧。与 ZIP 需要在后台执行的操作相比,检查文件夹模式是微不足道的。对于那些关心不浪费处理器、磁盘、电力或需要在同一台机器上与其他程序一起运行的人,我强烈建议不要这样做。另外,如果你在面试时被问到这类问题,千万不要说“我的代码很简单,所以我不关心处理器时间”——你不会得到这份工作。
我切换到 answer provided by @justin-r。不过,我会把这个答案留在那里,作为另一种方式
如果文件夹位于不同的网络共享上并包含大量文件,我认为这将是最佳选择。
C
Chris S

对 d4nt 的回答进行了小幅改进,因为如果您在服务器和开发机器上工作,您可能想要检查错误并且不必更改 xcopy 路径:

public void CopyFolder(string source, string destination)
{
    string xcopyPath = Environment.GetEnvironmentVariable("WINDIR") + @"\System32\xcopy.exe";
    ProcessStartInfo info = new ProcessStartInfo(xcopyPath);
    info.UseShellExecute = false;
    info.RedirectStandardOutput = true;
    info.Arguments = string.Format("\"{0}\" \"{1}\" /E /I", source, destination);

    Process process = Process.Start(info);
    process.WaitForExit();
    string result = process.StandardOutput.ReadToEnd();

    if (process.ExitCode != 0)
    {
        // Or your own custom exception, or just return false if you prefer.
        throw new InvalidOperationException(string.Format("Failed to copy {0} to {1}: {2}", source, destination, result));
    }
}

K
Khoi_Vjz_Boy

这是我的代码希望这有帮助

    private void KCOPY(string source, string destination)
    {
        if (IsFile(source))
        {
            string target = Path.Combine(destination, Path.GetFileName(source));
            File.Copy(source, target, true);
        }
        else
        {
            string fileName = Path.GetFileName(source);
            string target = System.IO.Path.Combine(destination, fileName);
            if (!System.IO.Directory.Exists(target))
            {
                System.IO.Directory.CreateDirectory(target);
            }

            List<string> files = GetAllFileAndFolder(source);

            foreach (string file in files)
            {
                KCOPY(file, target);
            }
        }
    }

    private List<string> GetAllFileAndFolder(string path)
    {
        List<string> allFile = new List<string>();
        foreach (string dir in Directory.GetDirectories(path))
        {
            allFile.Add(dir);
        }
        foreach (string file in Directory.GetFiles(path))
        {
            allFile.Add(file);
        }

        return allFile;
    }
    private bool IsFile(string path)
    {
        if ((File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory)
        {
            return false;
        }
        return true;
    }

查看所选答案,通过在搜索文件夹和文件时使用 SearchOption 标志,它在 4 行代码中执行此操作。现在还可以查看枚举上的 .HasFlag 扩展。
t
toddmo

如果您喜欢 Konrad 的流行答案,但您希望 source 本身成为 target 下的文件夹,而不是将其子项放在 target 文件夹下,下面是代码。它返回新创建的 DirectoryInfo,这很方便:

public static DirectoryInfo CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
  var newDirectoryInfo = target.CreateSubdirectory(source.Name);
  foreach (var fileInfo in source.GetFiles())
    fileInfo.CopyTo(Path.Combine(newDirectoryInfo.FullName, fileInfo.Name));

  foreach (var childDirectoryInfo in source.GetDirectories())
    CopyFilesRecursively(childDirectoryInfo, newDirectoryInfo);

  return newDirectoryInfo;
}

i
iato

您始终可以使用来自 Microsoft 网站的 this

static void Main()
{
    // Copy from the current directory, include subdirectories.
    DirectoryCopy(".", @".\temp", true);
}

private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
    // Get the subdirectories for the specified directory.
    DirectoryInfo dir = new DirectoryInfo(sourceDirName);

    if (!dir.Exists)
    {
        throw new DirectoryNotFoundException(
            "Source directory does not exist or could not be found: "
            + sourceDirName);
    }

    DirectoryInfo[] dirs = dir.GetDirectories();
    // If the destination directory doesn't exist, create it.
    if (!Directory.Exists(destDirName))
    {
        Directory.CreateDirectory(destDirName);
    }

    // Get the files in the directory and copy them to the new location.
    FileInfo[] files = dir.GetFiles();
    foreach (FileInfo file in files)
    {
        string temppath = Path.Combine(destDirName, file.Name);
        file.CopyTo(temppath, false);
    }

    // If copying subdirectories, copy them and their contents to new location.
    if (copySubDirs)
    {
        foreach (DirectoryInfo subdir in dirs)
        {
            string temppath = Path.Combine(destDirName, subdir.Name);
            DirectoryCopy(subdir.FullName, temppath, copySubDirs);
        }
    }
}

太好了——请记住 file.CopyTo(temppath, false); 行说“将此文件复制到此位置,仅当它不存在时”,大多数情况下这不是我们想要的。但是,我可以理解为什么它默认为那个。也许在覆盖文件的方法中添加一个标志。
k
kofifus

这是一个简洁有效的解决方案:

namespace System.IO {
  public static class ExtensionMethods {

    public static void CopyTo(this DirectoryInfo srcPath, string destPath) {
      Directory.CreateDirectory(destPath);
      Parallel.ForEach(srcPath.GetDirectories("*", SearchOption.AllDirectories), 
        srcInfo => Directory.CreateDirectory($"{destPath}{srcInfo.FullName[srcPath.FullName.Length..]}"));
      Parallel.ForEach(srcPath.GetFiles("*", SearchOption.AllDirectories), 
        srcInfo => File.Copy(srcInfo.FullName, $"{destPath}{srcInfo.FullName[srcPath.FullName.Length..]}", true));
      });
    }

  }
}

要使用:

new DirectoryInfo(sourcePath).CopyTo(destinationPath);

V
Vinko Vrsalovic

很抱歉之前的代码,它仍然有错误 :((陷入了最快的枪问题)。在这里它经过测试并且可以工作。关键是 SearchOption.AllDirectories,它消除了显式递归的需要。

string path = "C:\\a";
string[] dirs = Directory.GetDirectories(path, "*.*", SearchOption.AllDirectories);
string newpath = "C:\\x";
try
{
    Directory.CreateDirectory(newpath);
}
catch (IOException ex)
{
    Console.WriteLine(ex.Message);
}
for (int j = 0; j < dirs.Length; j++)
{
    try
    {
        Directory.CreateDirectory(dirs[j].Replace(path, newpath));
    }
    catch (IOException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

string[] files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
for (int j = 0; j < files.Length; j++)            
{
    try
    {
        File.Copy(files[j], files[j].Replace(path, newpath));
    }
    catch (IOException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

D
Daryl

这是 DirectoryInfo a la FileInfo.CopyTo 的扩展方法(注意 overwrite 参数):

public static DirectoryInfo CopyTo(this DirectoryInfo sourceDir, string destinationPath, bool overwrite = false)
{
    var sourcePath = sourceDir.FullName;

    var destination = new DirectoryInfo(destinationPath);

    destination.Create();

    foreach (var sourceSubDirPath in Directory.EnumerateDirectories(sourcePath, "*", SearchOption.AllDirectories))
        Directory.CreateDirectory(sourceSubDirPath.Replace(sourcePath, destinationPath));

    foreach (var file in Directory.EnumerateFiles(sourcePath, "*", SearchOption.AllDirectories))
        File.Copy(file, file.Replace(sourcePath, destinationPath), overwrite);

    return destination;
}

A
Ahmed Sabry

使用这个类。

public static class Extensions
{
    public static void CopyTo(this DirectoryInfo source, DirectoryInfo target, bool overwiteFiles = true)
    {
        if (!source.Exists) return;
        if (!target.Exists) target.Create();

        Parallel.ForEach(source.GetDirectories(), (sourceChildDirectory) => 
            CopyTo(sourceChildDirectory, new DirectoryInfo(Path.Combine(target.FullName, sourceChildDirectory.Name))));

        foreach (var sourceFile in source.GetFiles())
            sourceFile.CopyTo(Path.Combine(target.FullName, sourceFile.Name), overwiteFiles);
    }
    public static void CopyTo(this DirectoryInfo source, string target, bool overwiteFiles = true)
    {
        CopyTo(source, new DirectoryInfo(target), overwiteFiles);
    }
}

这类似于其他答案,被重构为使用 .ToList().ForEach( (与直接枚举目录相比,它的工作量、内存和速度稍慢一些)并作为扩展方法。所选答案使用 SearchOption.AllDirectories 并避免递归,因此我建议切换到该模型。此外,您通常不需要扩展方法中的类型名称 - 我会将其重命名为 CopyTo() 以便它变为 sourceDir.CopyTo(destination);
T
Termininja

一种只有一个循环的变体,用于复制所有文件夹和文件:

foreach (var f in Directory.GetFileSystemEntries(path, "*", SearchOption.AllDirectories))
{
    var output = Regex.Replace(f, @"^" + path, newPath);
    if (File.Exists(f)) File.Copy(f, output, true);
    else Directory.CreateDirectory(output);
}

如果您要使用 Regex,您可能还应该将 Regex.Escape(path) 作为表达式组合的一部分(特别是考虑到 Windows 路径分隔符)。您可能还可以从在循环外创建(并且可能编译)new Regex() 对象而不是依赖静态方法中受益。
m
malballah

比任何代码都好(使用递归的 DirectoryInfo 的扩展方法)

public static bool CopyTo(this DirectoryInfo source, string destination)
    {
        try
        {
            foreach (string dirPath in Directory.GetDirectories(source.FullName))
            {
                var newDirPath = dirPath.Replace(source.FullName, destination);
                Directory.CreateDirectory(newDirPath);
                new DirectoryInfo(dirPath).CopyTo(newDirPath);
            }
            //Copy all the files & Replaces any files with the same name
            foreach (string filePath in Directory.GetFiles(source.FullName))
            {
                File.Copy(filePath, filePath.Replace(source.FullName,destination), true);
            }
            return true;
        }
        catch (IOException exp)
        {
            return false;
        }
    }

除了使用递归(在不需要的地方)和隐藏异常以使调试更加困难之外,我不确定这对接受的答案增加了什么。
L
Lakmal

复制并替换文件夹的所有文件

        public static void CopyAndReplaceAll(string SourcePath, string DestinationPath, string backupPath)
    {
            foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories))
            {
                Directory.CreateDirectory($"{DestinationPath}{dirPath.Remove(0, SourcePath.Length)}");
                Directory.CreateDirectory($"{backupPath}{dirPath.Remove(0, SourcePath.Length)}");
            }
            foreach (string newPath in Directory.GetFiles(SourcePath, "*.*", SearchOption.AllDirectories))
            {
                if (!File.Exists($"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}"))
                    File.Copy(newPath, $"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}");
                else
                    File.Replace(newPath
                        , $"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}"
                        , $"{ backupPath}{newPath.Remove(0, SourcePath.Length)}", false);
            }
    }

为答案欢呼,但我不确定这增加了什么。 try catch throw 也是毫无意义的。
A
Arash.Zandi

下面的代码是 microsoft 建议 how-to-copy-directories,它由亲爱的 @iato 共享,但它只是递归复制源文件夹的子目录和文件,并且不会复制它自己的源文件夹(如右键单击 -> 复制)。

但在这个答案下面有一个棘手的方法:

private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs = true)
        {
            // Get the subdirectories for the specified directory.
            DirectoryInfo dir = new DirectoryInfo(sourceDirName);

            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException(
                    "Source directory does not exist or could not be found: "
                    + sourceDirName);
            }

            DirectoryInfo[] dirs = dir.GetDirectories();
            // If the destination directory doesn't exist, create it.
            if (!Directory.Exists(destDirName))
            {
                Directory.CreateDirectory(destDirName);
            }

            // Get the files in the directory and copy them to the new location.
            FileInfo[] files = dir.GetFiles();
            foreach (FileInfo file in files)
            {
                string temppath = Path.Combine(destDirName, file.Name);
                file.CopyTo(temppath, false);
            }

            // If copying subdirectories, copy them and their contents to new location.
            if (copySubDirs)
            {
                foreach (DirectoryInfo subdir in dirs)
                {
                    string temppath = Path.Combine(destDirName, subdir.Name);
                    DirectoryCopy(subdir.FullName, temppath, copySubDirs);
                }
            }
        }

如果您想递归地复制源文件夹和子文件夹的内容,您可以像这样简单地使用它:

string source = @"J:\source\";
string dest= @"J:\destination\";
DirectoryCopy(source, dest);

但是如果你想复制它自己的源目录(类似于你右键单击源文件夹并单击复制然后在你单击粘贴的目标文件夹中)你应该像这样使用:

 string source = @"J:\source\";
 string dest= @"J:\destination\";
 DirectoryCopy(source, Path.Combine(dest, new DirectoryInfo(source).Name));

已经在下面发布了一些答案:stackoverflow.com/a/45199038/1951524
谢谢@MA-Maddin,但它会复制源文件夹本身吗?还是只是内容?
仅供参考,VB.NET 的新 Microsoft.VisualBasic.Devices.Computer().FileSystem.CopyDirectory 具有覆盖/运送选项和进度条显示......这些 c# 代码并不完全等效。
R
Rahul Shukla

下面的代码将所有文件从同一文件夹结构中的给定模式的源复制到目标:

public static void Copy()
        {
            string sourceDir = @"C:\test\source\";
            string destination = @"C:\test\destination\";

            string[] textFiles = Directory.GetFiles(sourceDir, "*.txt", SearchOption.AllDirectories);

            foreach (string textFile in textFiles)
            {
                string fileName = textFile.Substring(sourceDir.Length);
                string directoryPath = Path.Combine(destination, Path.GetDirectoryName(fileName));
                if (!Directory.Exists(directoryPath))
                    Directory.CreateDirectory(directoryPath);

                File.Copy(textFile, Path.Combine(directoryPath, Path.GetFileName(textFile)), true);
            }
        }

https://i.stack.imgur.com/LlIax.png


这与接受的答案有何不同?
M
M. Mennan Kara

只是想添加我的版本。它可以处理目录和文件,如果目标文件存在,它可以覆盖或跳过。

public static void Copy(
    string source,
    string destination,
    string pattern = "*",
    bool includeSubFolders = true,
    bool overwrite = true,
    bool overwriteOnlyIfSourceIsNewer = false)
{
    if (File.Exists(source))
    {
        // Source is a file, copy and leave
        CopyFile(source, destination);
        return;
    }

    if (!Directory.Exists(source))
    {
        throw new DirectoryNotFoundException($"Source directory does not exists: `{source}`");
    }

    var files = Directory.GetFiles(
        source,
        pattern,
        includeSubFolders ?
            SearchOption.AllDirectories :
            SearchOption.TopDirectoryOnly);

    foreach (var file in files)
    {
        var newFile = file.Replace(source, destination);
        CopyFile(file, newFile, overwrite, overwriteOnlyIfSourceIsNewer);
    }
}

private static void CopyFile(
    string source,
    string destination,
    bool overwrite = true,
    bool overwriteIfSourceIsNewer = false)
{
    if (!overwrite && File.Exists(destination))
    {
        return;
    }

    if (overwriteIfSourceIsNewer && File.Exists(destination))
    {
        var sourceLastModified = File.GetLastWriteTimeUtc(source);
        var destinationLastModified = File.GetLastWriteTimeUtc(destination);
        if (sourceLastModified <= destinationLastModified)
        {
            return;
        }

        CreateDirectory(destination);
        File.Copy(source, destination, overwrite);
        return;
    }

    CreateDirectory(destination);
    File.Copy(source, destination, overwrite);
}

private static void CreateDirectory(string filePath)
{
    var targetDirectory = Path.GetDirectoryName(filePath);
    if (targetDirectory != null && !Directory.Exists(targetDirectory))
    {
        Directory.CreateDirectory(targetDirectory);
    }
}

R
Rui Caramalho

此代码的属性:

没有并行任务,性能较差,但想法是逐个文件处理,因此您可以记录或停止。

可以跳过隐藏文件

可以按修改日期跳过

文件复制错误是否可以中断(您选择)

为 SMB 和 FileShare.ReadWrite 使用 64K 缓冲区以避免锁定

个性化您的异常消息

对于 Windows

注意 ExceptionToString() 是一个个人扩展,它试图获取内部异常并显示堆栈。将其替换为 ex.Message 或任何其他代码。 log4net.ILog _log 我使用 ==Log4net== 你可以用不同的方式制作你的日志。

/// <summary>
/// Recursive Directory Copy
/// </summary>
/// <param name="fromPath"></param>
/// <param name="toPath"></param>
/// <param name="continueOnException">on error, continue to copy next file</param>
/// <param name="skipHiddenFiles">To avoid files like thumbs.db</param>
/// <param name="skipByModifiedDate">Does not copy if the destiny file has the same or more recent modified date</param>
/// <remarks>
/// </remarks>
public static void CopyEntireDirectory(string fromPath, string toPath, bool continueOnException = false, bool skipHiddenFiles = true, bool skipByModifiedDate = true)
{
    log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    string nl = Environment.NewLine;

    string sourcePath = "";
    string destPath = "";
    string _exMsg = "";

    void TreateException(Exception ex)
    {
        _log.Warn(_exMsg);
        if (continueOnException == false)
        {
            throw new Exception($"{_exMsg}{nl}----{nl}{ex.ExceptionToString()}");
        }
    }

    try
    {
        foreach (string fileName in Directory.GetFileSystemEntries(fromPath, "*", SearchOption.AllDirectories))
        {
            sourcePath = fileName;
            destPath = Regex.Replace(fileName, "^" + Regex.Escape(fromPath), toPath);

            Directory.CreateDirectory(Path.GetDirectoryName(destPath));
            
            _log.Debug(FileCopyStream(sourcePath, destPath,skipHiddenFiles,skipByModifiedDate));
        }
    }
    // Directory must be less than 148 characters, File must be less than 261 characters
    catch (PathTooLongException)
    {
        throw new Exception($"Both paths must be less than 148 characters:{nl}{sourcePath}{nl}{destPath}");
    }
    // Not enough disk space. Cancel further copies
    catch (IOException ex) when ((ex.HResult & 0xFFFF) == 0x27 || (ex.HResult & 0xFFFF) == 0x70)
    {
        throw new Exception($"Not enough disk space:{nl}'{toPath}'");
    }
    // used by another process
    catch (IOException ex) when ((uint)ex.HResult == 0x80070020)
    {
        _exMsg = $"File is being used by another process:{nl}'{destPath}'{nl}{ex.Message}";
        TreateException(ex);
    }
    catch (UnauthorizedAccessException ex)
    {
        _exMsg = $"Unauthorized Access Exception:{nl}from:'{sourcePath}'{nl}to:{destPath}";
        TreateException(ex);
    }
    catch (Exception ex)
    {
        _exMsg = $"from:'{sourcePath}'{nl}to:{destPath}";
        TreateException(ex);
    }
}

/// <summary>
/// File Copy using Stream 64K and trying to avoid locks with fileshare
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="destPath"></param>
/// <param name="skipHiddenFiles">To avoid files like thumbs.db</param>
/// <param name="skipByModifiedDate">Does not copy if the destiny file has the same or more recent modified date</param>
public static string FileCopyStream(string sourcePath, string destPath, bool skipHiddenFiles = true, bool skipByModifiedDate = true)
{
    // Buffer should be 64K = 65536‬ bytes 
    // Increasing the buffer size beyond 64k will not help in any circunstance,
    // as the underlying SMB protocol does not support buffer lengths beyond 64k."
    byte[] buffer = new byte[65536];

    if (!File.Exists(sourcePath))
        return $"is not a file: '{sourcePath}'";

    FileInfo sourcefileInfo = new FileInfo(sourcePath);
    FileInfo destFileInfo = null;
    if (File.Exists(destPath))
        destFileInfo = new FileInfo(destPath);

    if (skipHiddenFiles)
    {
        if (sourcefileInfo.Attributes.HasFlag(FileAttributes.Hidden))
            return $"Hidden File Not Copied: '{sourcePath}'";
    }

    using (FileStream input = sourcefileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    using (FileStream output = new FileStream(destPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, buffer.Length))
    {
        if (skipByModifiedDate && destFileInfo != null)
        {
            if (destFileInfo.LastWriteTime < sourcefileInfo.LastWriteTime)
            {
                input.CopyTo(output, buffer.Length);
                destFileInfo.LastWriteTime = sourcefileInfo.LastWriteTime;
                return $"Replaced: '{sourcePath}'";
            }
            else
            {
                return $"NOT replaced (more recent or same file): '{sourcePath}'";
            }
        }
        else
        {
            input.CopyTo(output, buffer.Length);
            destFileInfo = new FileInfo(destPath);
            destFileInfo.LastWriteTime = sourcefileInfo.LastWriteTime;
            return $"New File: '{sourcePath}'";
        }
    }
}