在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个回答

0
public static class Extensions
{
    public static void Copy(this DirectoryInfo self, DirectoryInfo destination, bool recursively)
    {
        foreach (var file in self.GetFiles())
        {
            file.CopyTo(Path.Combine(destination.FullName, file.Name));
        }

        if (recursively)
        {
            foreach (var directory in self.GetDirectories())
            {
                directory.Copy(destination.CreateSubdirectory(directory.Name), recursively);
            }
        }
    }
}

使用示例:

var sourceDirectory = new DirectoryInfo(@"C:\source");
var destinationDirectory = new DirectoryInfo(@"C:\destination");

if (destinationDirectory.Exists == false)
{
    sourceDirectory.Copy(destinationDirectory, recursively: true);
}

0
以下代码将源文件夹中符合指定模式的所有文件复制到目标文件夹,保持相同的文件夹结构。
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);
            }
        }

enter image description here


这与被接受的答案有何不同? - Keith

0

对于使用 Async API 的 UWP 和 Winui 3(WindowsAppSdk):

public async Task CopyAsync(StorageFolder source, StorageFolder dest)
{
    foreach (var item in await source.GetItemsAsync())

        if (item is StorageFile file)
            await file.CopyAsync(dest);

        else if (item is StorageFolder folder)
            await CopyAsync(folder, await dest.CreateFolderAsync(folder.Name, CreationCollisionOption.OpenIfExists));
}

0

我想分享我的版本。它可以处理目录和文件,并且可以根据需要覆盖或跳过已存在的目标文件。

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

0

比任何代码都好(带有递归的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;
        }
    }

1
我不确定这个回答相比被接受的答案有什么额外的贡献,除了使用递归(在不需要的情况下)并隐藏异常以使调试更加困难。 - Keith

0
以下是微软的建议如何复制目录的代码,由亲爱的@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));

已经在下面发布了一些答案:https://dev59.com/kHVD5IYBdhLWcg3wL4iM#45199038 - Martin Schneider
谢谢@MA-Maddin,但它是复制源文件夹本身还是只是其中的内容? - Arash.Zandi
FYI,VB.NET 的新 Microsoft.VisualBasic.Devices.Computer().FileSystem.CopyDirectory 具有覆盖/Ship 选项和进度条显示...那些 c# 代码并不完全等价。 - Jonney

0

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

        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是没有意义的。 - Keith

0

此代码的特点:

  • 没有并行任务,性能较差,但是想法是逐个文件处理,这样您可以记录或停止。
  • 可以跳过隐藏文件
  • 可以根据修改日期跳过
  • 在文件复制错误时可以选择中断或不中断
  • 使用 64K 的缓冲区进行 SMB 和 FileShare.ReadWrite,以避免锁定
  • 个性化您的异常消息
  • 适用于 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}'";
        }
    }
}

0
为了完整性,使用相对文件路径并可选地替换文件:
        public static void DuplicateDirectories(
            string sourceDirectory, 
            string targetDirectory,
            string searchPattern = "*.*",
            SearchOption searchOption = SearchOption.AllDirectories)
        {
            foreach (string dir in Directory.GetDirectories(sourceDirectory, searchPattern, searchOption)) 
            {
                var relativePath = Path.GetRelativePath(sourceDirectory, dir);
                var targetPath = Path.Combine(targetDirectory, relativePath);
                Directory.CreateDirectory(targetPath);
            }
        }

        public static void CopyFilesToDirectories(
            string sourceDirectory, 
            string targetDirectory,
            bool replaceIfExists,
            string searchPattern = "*.*",
            SearchOption searchOption = SearchOption.AllDirectories)
        {
            foreach (string filePath in Directory.GetFiles(sourceDirectory, searchPattern, searchOption))
            {
                var relativePath = Path.GetRelativePath(sourceDirectory, filePath);
                var targetPath = Path.Combine(targetDirectory, relativePath);
                File.Copy(filePath, targetPath, replaceIfExists);
            }
        }

然后:


      var sourceDirectory = @"path\to\source";
      var targetDirectory = @"path\to\target";

       DuplicateDirectories(
            sourceDirectory,
            targetDirectory);

        CopyFilesToDirectories(
            sourceDirectory,
            targetDirectory,
            true);

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