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

624

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

使用System.IO类似乎没有一种不需要大量递归的方法来实现此目的。

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

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

这似乎是一个相当丑陋的黑科技。有没有更好的方法?


117
看下面发布的替代方案,我认为使用VB的方式并不那么丑陋。 - Kevin Kershaw
47
当它是.NET框架的一部分时,怎么可能算作黑客行为呢?停止编写代码,利用你所拥有的东西。 - AMissico
15
这是一个常见的误解。Microsft.VisualBasic包含所有常用的Visual Basic过程,使得使用VB进行编码变得更加容易。Microsot.VisualBasic.Compatibility是用于VB6遗留代码的程序集。 - AMissico
72
微软.VisualBasic.Devices.Computer.FileSystem有超过2000行的代码。CopyDirectory函数确保不会将父文件夹复制到子文件夹中,并进行其他检查。该功能经过高度优化等。所选答案最多只能算是脆弱的代码。 - AMissico
21
为什么这个被优化且完整的代码在Microsoft.VisualBasic而不是 System.IO中?之所以没有出现在Mono中是因为所有被认为是“核心”的库都是System.[something] - 所有其他的库都不是。我并不介意引用额外的DLL,但微软没有将这个功能包含在System.IO中,这其中有一个很好的原因。 - Keith
显示剩余19条评论
29个回答

4

我的解决方案基本上是对@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));
}

3

对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));
    }
}

3

它可能不是注重性能,但我正在使用它处理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在System.IO.Compression命名空间中仅适用于.NET 4.5及以上版本


1
我也不知道,所以才问的,但是所选答案并不需要递归。这个答案在磁盘上创建了一个zip文件,这是进行文件复制的大量额外工作 - 不仅会创建数据的额外副本,而且还会花费处理器时间来压缩和解压缩它。我相信它可以工作,就像你可能可以用鞋子敲钉子一样,但这是更多的工作,有更多的事情可能会出错,而有更好的方法来完成它。 - Keith
我最终采用这种方法是因为字符串替换。正如其他人指出的那样,被接受的答案存在许多问题;连接链接可能无法工作,重复的文件夹模式或没有扩展名或名称的文件也可能会出现问题。代码越少,出错的机会就越小。而且,由于处理器时间对我来说不是一个问题,所以它适用于我的特定情况。 - AlexanderD
2
是的,这就像开了1000英里的路绕过一个红绿灯一样,但这是你的旅程,所以去吧。与ZIP在内部执行的操作相比,检查文件夹模式微不足道。我强烈建议任何关心不浪费处理器、磁盘、电力或需要在同一台机器上与其他程序并行运行的人都不要这样做。此外,如果你在面试中被问到这种类型的问题,“我的代码很简单所以我不在乎处理器时间”的回答是 永远 不要选的 - 你不会得到这份工作的。 - Keith
1
我转而使用@justin-r提供的答案。不过,我会将这个答案保留在那里,作为另一种解决方法。 - AlexanderD
4
如果这些文件夹存储在不同的网络共享文件夹中并且包含许多文件,我认为这将是最佳选择。 - Danny Parker
非常有创意!就正确性而言,这可能是第一名的答案,我确信它可以处理其他简单粗暴答案忽略的1000个边缘情况。或者执行 robocopy.exe(如果跨平台不是问题)。 - Ohad Schneider

2
如果您喜欢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;
}

2
您可以随时使用来源于微软网站的这个指南来进行操作。
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);
        }
    }
}

2
这很棒--请记住,行file.CopyTo(temppath, false);表示“将此文件复制到此位置,仅当它不存在时”,这在大多数情况下并不是我们想要的。但是,我可以理解为什么默认为这样。也许可以向该方法添加一个标志以覆盖文件。 - Andy

2

这是我的代码,希望能对你有所帮助。

    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”扩展。 - Keith

1
使用这个类。
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);
    }
}

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

1

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

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() 对象中获得好处,而不是依赖于静态方法。 - jimbobmcgee

1

很抱歉之前的代码还有 bug :((遭受了最快枪问题)。这里是经过测试和工作的代码。关键是使用 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);
    }
}

1
这是一个针对 DirectoryInfo 的扩展方法,类似于 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接