Я хочу скопировать все содержимое каталога из одного места в другое на C#.
Похоже, что нет способа сделать это с использованием классов System.IO без большой рекурсии.
В VB есть метод, который мы можем использовать, если добавим ссылку на Microsoft.VisualBasic:
new Microsoft.VisualBasic.Devices.Computer().
FileSystem.CopyDirectory( sourceFolder, outputFolder );
Это кажется довольно уродливым взломом. Есть ли способ лучше?
Реальный вопрос: почему этого нет в библиотеке ввода-вывода по умолчанию? К настоящему времени мы, вероятно, все поместили один и тот же код в нашу личную библиотеку.
Как это может быть взлом, если он является частью .NET Framework? Прекратите писать код и используйте то, что у вас есть.
Microsoft.VisualBasic - это набор дополнений для упрощения обновления устаревших проектов VB6. Обычно вы не используете его в приложении C#. Если бы это была «правильная» часть инфраструктуры .Net, она была бы в System.IO. Также только пространства имен System.[something] являются частью Mono.
Это распространенное заблуждение. Microsft.VisualBasic содержит все стандартные процедуры Visual Basic, которые значительно упрощают кодирование на VB. Microsot.VisualBasic.Compatibility - это сборка, используемая для устаревших версий VB6.
Если вы посмотрите на источник CopyDirectory, вы увидите, что он использует System.IO или внутренние вызовы Shell API, в зависимости от того, как вызывается CopyDirectory.
Причина, по которой Microsoft.VisualBasic не добавляется в проект C#, заключается в том, что это не проект VB. Даже проекты VB.NET должны добавить Microsoft.VisualBasic.Compatibility, если они хотят использовать унаследованные функции. Уровень совместимости добавляется только мастером миграции или пользователем.
В Microsoft.VisualBasic.Devices.Computer.FileSystem содержится более 2000 строк кода. CopyDirectory гарантирует, что вы не копируете родительскую папку в дочернюю и другие проверки. Он сильно оптимизирован и так далее. Выбранный ответ - в лучшем случае хрупкий код.
Это ограничение Mono. Не является частью вашего ответа. Вы хотите лучшего, но его нет.
Если вам нужен System.Windows.Forms.Design, собираетесь ли вы избегать добавления этой ссылки только потому, что в ней есть слова «Дизайн»? Конечно, нет. Следовательно, избегать чего-то, что встроено в фреймворк, только потому, что в его названии есть слово «VisualBasic», ну ... просто ... глупо.
Вы, ребята, C#, просто убейте меня. Дело не в инструментах, а в решениях.
@AMissico - хорошо, тогда почему этот оптимизированный и полный код находится в Microsoft.VisualBasic, а не в System.IO? Причина, по которой его нет в Mono, заключается в том, что все библиотеки, которые считаются «основными», относятся к System.[something], а все остальные - нет. У меня нет проблем со ссылкой на дополнительную DLL, но есть веская причина, по которой Microsoft не включила эту функцию в System.IO.
Всем тем, кто считает, что использование Microsoft.VisualBasic - это нормально: были бы вы счастливы, используя библиотеку из Perl в python? Это в основном то, что происходит, с небольшими отличиями. Кроме того, не используя библиотеки System.*, они потенциально ограничивают себя от использования Mono, что, как я понимаю, может быть проблемой из комментариев, сделанных OP.
@RCIX: Лучшие разработчики - это те, кто выполняет свою работу эффективно и быстро. Так что, если в имя есть Visual Basic, на самом деле код в DLL - это просто MSIL. Microsoft явно написала эффективный алгоритм, и было бы стыдно, если бы кто-то упустил его из виду только потому, что в его названии есть слово Visual Basic. Тем более, если алгоритм, который он пишет только из-за странности имени DLL, оказывается ошибочным и требует больше времени на исправление.
@Keith: Возможно, BCL был доработан до того, как они обнаружили, что эта функция нужна. Все, что мы можем сделать, это предположить на этом этапе. У вас есть веская причина, по которой вы не должны включать DLL, кроме имени? В документации MSDN сказано, что эта функция устарела?
@jasonh: нет - это уже довольно старый вопрос, и ссылка на Microsoft.VisualBasic присутствует в поставляемом программном обеспечении уже более года. Мне просто интересно, почему он находится в странном месте - это должно быть что-то вроде System.IO.Directory.Copy(sourceFolder, outputFolder)
Я согласен с тем, что это должно быть, но определенно правдоподобно, что BCL был доработан до того, как они поняли, что это будет необходимо для VB и, следовательно, его полезности, и они никогда не думали возвращаться к нему. Просто теоретизирую, заметьте. :)
Я просто ответил на вопросы с некоторыми опциями, подобными xcopy, в C#. stackoverflow.com/questions/22151995/…
Я использую robocopy, он отлично работает
Обратите внимание, что Microsoft.VisualBasic.Devices недоступен в ядре .NET: github.com/dotnet/docs/issues/14546
Использование этого кода в производственной среде для копирования вложенных каталогов и файлов. Не обнаружил никаких проблем. docs.microsoft.com/en-us/dotnet/standard/io/…





Или, если вы хотите пойти другим путем, добавьте ссылку на свой проект для Microsoft.VisualBasic, а затем используйте следующее:
Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(fromDirectory, toDirectory);
Однако использование одной из рекурсивных функций - лучший способ, поскольку ей не нужно загружать dll VB.
В любом случае это не сильно отличается от того, как я это делал - вам все равно нужно загрузить материал обратной совместимости VB, чтобы иметь возможность это сделать.
Дорогая ли загрузка сборки VB? Параметры VB намного элегантнее, чем версии C#.
Что за "обратная совместимость VB"? CopyDirectory использует оболочку или платформу.
Хотелось бы, чтобы это было на System.IO.Directory, но это лучше, чем его переписывать!
Это путь, по-моему, намного проще, чем любой другой вариант.
Это способ! Возможно, нам стоит создать запрос UserVoice или github, чтобы добавить этот код в system.io.directory!
Извините за предыдущий код, в нем все еще были ошибки :( (стал жертвой самой быстрой проблемы с оружием). Здесь он протестирован и работает. Ключ - 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);
}
}
Вот служебный класс, который я использовал для подобных задач ввода-вывода.
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 использует SHFileOperation внутри Microsoft.VisualBasic.
Хм, думаю, я неправильно понял вопрос, но рискну. Что плохого в следующем простом методе?
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 выполняет некоторые дополнительные тесты правильности (например, являются ли исходный и целевой каталог допустимыми, является ли источник родительским для целевого объекта и т. д.), Которые отсутствуют в этом ответе. Этот код, вероятно, также более оптимизирован.
При этом код работает хорошо. Это имеет (почти идентично) использовалось в зрелом программном обеспечении в течение многих лет. Помимо присущей непостоянству всех операций ввода-вывода (например, что произойдет, если пользователь вручную отключит USB-накопитель, пока ваш код пишет на него?), Известных проблем нет.
В частности, я хотел бы отметить, что использование рекурсии здесь абсолютно не проблема. Ни в теории (концептуально это наиболее элегантное решение), ни на практике: этот код не переполняет стек. Стек достаточно велик, чтобы обрабатывать даже глубоко вложенные файловые иерархии. Задолго до того, как пространство стека станет проблемой, срабатывает ограничение длины пути к папке.
Обратите внимание, что злоумышленник может нарушить это предположение, используя глубоко вложенные каталоги по одной букве в каждом. Я этого не пробовал. Но просто чтобы проиллюстрировать суть дела: для того, чтобы вызвать переполнение этого кода на типичном компьютере, каталоги должны быть вложены несколько раз тысяча. Это просто нереальный сценарий.
Теперь, кроме рекурсивного вызова. Зачем это нужно делать на C#?
Это головная рекурсия. Он может стать жертвой переполнения стека, если каталоги достаточно глубоко вложены.
До недавнего времени глубина вложения каталогов ограничивалась ОС. Я сомневаюсь, что вы найдете каталоги, которые вложены более нескольких сотен раз (если даже). Приведенный выше код может занимать много больше.
Мне нравится рекурсивный подход, риск переполнения стека в худшем случае минимален.
@DTashkinov: ну извините, но это кажется излишним. Почему очевидный код == отрицательный? Противоположное должно быть правдой. Встроенный метод уже был опубликован, но Кейт специально попросил метод еще один. Кроме того, что вы имеете в виду под своим последним предложением? Извините, но я просто не понимаю ваших причин для голосования против.
Извините, если не понимаете правил голосования за и против. Сам опубликованный код выглядит нормально. Я хотел сказать, что написание собственного кода для тривиальных задач - это первое искушение, которое возникает перед ними, но последнее, что нужно сделать. Лично я посетил эту страницу, пытаясь найти встроенное решение, и был разочарован.
Это не лучший способ. Период. Используйте отлаженный и готовый к работе код, который Microsoft предоставила в Framework.
@AMissico: лучше, чем Какие? Никто не утверждал, что это лучше, чем код VB из фреймворка. У нас знать это не так.
Кажется разумным подходом ЕСЛИ, вы не хотите использовать код VB, упомянутый в вопросе. Я не понимаю возражений против рекурсии. Этот код никогда не взорвет стек, если вы не столкнетесь с ситуацией с символической ссылкой (также известной как точка повторной обработки) на родительский каталог, и достаточно легко избежать этого, а также подсчитать глубину рекурсии для защиты от других странных циклов. Microsoft рекомендует рекурсию в своем ответе HowTo: msdn.microsoft.com/en-us/library/vstudio/…
Проголосовали за и добавили отвечать, который является разновидностью этого, где source сам становится папкой в target, а затем его дочерние элементы попадают под это.
Я не совсем уверен, почему и как, но когда я пытался, код иногда давал сбой, пытаясь скопировать файлы до того, как каталог действительно был создан - мне пришлось добавить Directory.CreateDirectory (target.FullName); ко второму оператору if, чтобы код всегда работал. Любите рекурсивный код, отличное решение
@TheLemon В настоящее время код предполагает, что целевой каталог верхнего уровня уже существует. Если код вызван с несуществующей целью, он завершится ошибкой (и добавление проверки для этого может быть хорошей идеей!). Однако подкаталоги созданы правильно: это то, что делает вызов target.CreateSubdirectory.
Попробуй это:
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? Перезаписать?
/ E указывает ему скопировать все подкаталоги (даже пустые). / I сообщает ему, что если место назначения не существует, создайте каталог с этим именем.
добавьте двойные кавычки, чтобы быть в безопасности.
Добавьте / Y, чтобы не получать запрос на перезапись существующих файлов. stackoverflow.com/q/191209/138938
Добавление параметра / d полезно только для копирования измененных файлов вместе с параметром / i
Я знаю, что опаздываю, но я тоже искал решение этой проблемы. Я думаю, у вас тоже должен быть /S, если вы собираетесь использовать /E: «Используйте / e с параметрами командной строки / s и / t» (technet.microsoft.com/en-us/library/bb491035.aspx)
Если кроссплатформенная поддержка не требуется, это лучший подход. Я бы использовал Робокопия, например, Robocopy C:\A C:\B /E
Извините, но это ужасно. Предполагается, что целевая система - это окна. Предполагается, что будущие версии включают xcopy.exe по этому конкретному пути. Предполагается, что параметры xcopy не меняются. Требуется собрать параметры для xcopy в виде строки, что создает большой потенциал ошибок. Также в образце не упоминается какая-либо обработка ошибок для результатов запущенного процесса, чего я ожидал, потому что, в отличие от других методов, это не помогло бы незаметно.
@MatthiasJansen, я думаю, ты принял это очень личное. Ответ по существу и многое объясняет о том, как этого добиться ... Поскольку вопрос не требует кросс-платформенной совместимости или использования xcopy или чего-то еще, плакат только что ответил, чтобы объяснить, как этого можно достичь одним способом ... Там может быть 1000 способов сделать то же самое, и ответы на них разные ... вот почему этот форум здесь, чтобы обратиться, и программисты со всего мира приезжают сюда, чтобы поделиться своим опытом. Я голосую против вашего комментария.
Это сработало отлично - спасибо за публикацию. Мне нужно было быстрое и грязное решение для утилиты, которую я пишу. Мне не нужно было много кода.
что лучше или быстрее, xcopy.exe или System.IO?
Скопировано с 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.
}
Нет причин проверять, существует ли каталог, просто вызовите Directory.CreateDirectory, который ничего не сделает, если каталог уже существует.
Для тех, кто хочет иметь дело с путями длиной более 256 символов, вы можете использовать пакет Nuget под названием ZetaLongPaths.
Этот ответ кажется самым полезным из всех. Использование DirectoryInfo вместо строк позволяет избежать множества потенциальных проблем.
Скопируйте папку рекурсивно без рекурсии, чтобы избежать переполнения стека.
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;
}
}
полезный шаблон без рекурсии :)
Трудно представить, как взорвать стек, прежде чем светится предел пути
Намного легче
//Now Create all of the directories
foreach (string dirPath in Directory.GetDirectories(SourcePath, "*",
SearchOption.AllDirectories))
Directory.CreateDirectory(dirPath.Replace(SourcePath, DestinationPath));
//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, DestinationPath), true);
Хорошая идея - не знаю, почему я никогда не думал об использовании SearchOption.AllDirectories. Я бы, вероятно, использовал метод SubString, а не Replace, но это просто стиль кодирования.
Это действительно хороший фрагмент кода, но это не тот код, который можно где угодно использовать. Разработчики должны быть осторожны, потому что dirPath.Replace может вызвать нежелательные последствия. Просто предупреждение для людей, которые любят копировать и вставлять в сети. Код, опубликованный @jaysponsored, безопаснее, потому что он не использует string.Replace, но я уверен, что у него также есть свои угловые случаи.
Потрясающие! Это решит мою проблему. Но есть ли способ копировать / перемещать файлы с миллиардами номеров и без удаления существующих файлов / папок?
Будьте осторожны с этим кодом, так как он вызовет исключение, если целевой каталог уже существует. Он также не будет перезаписывать уже существующие файлы. Просто добавьте проверку перед созданием каждого каталога и используйте перегрузку File.Copy для перезаписи целевого файла, если он существует.
@Alex - Что в этом случае делает String.Substring лучше String.Replace?
@Alex - именно поэтому я бы использовал метод SubString, а не Replace
@Xaisoft - у Replace есть проблема, если у вас есть повторяющийся шаблон внутри пути, например, "sourceDir/things/sourceDir/things" должен стать "destinationDir/things/sourceDir/things", но если вы используете замену, он станет "destinationDir/things/destinationDir/things"
@Rick этот метод выполняет File.Copy, поэтому он оставляет оригинал. Если вы хотите удалить оригинал, вы можете использовать File.Move или (возможно, лучше) добавить цикл для удаления файлов после завершения копирования.
Почему *.* вместо *? Разве вы не хотите копировать файлы без расширений?
Следует также отметить, что пути требуют обратной косой черты в конце. В противном случае имена файлов и папок будут добавлены к корневому имени назначения для каждого элемента. Просто совет!
@AaronLS параметр AllDirectories означает, что файлы могут находиться в подкаталогах исходного каталога. Использование вашего кода приведет к тому, что все файлы будут скопированы в сам целевой каталог, а не в подкаталог целевого каталога.
@Alex: в чем проблема с dirPath.Replace ??? Не могли бы вы подробнее рассказать о проблемах с этим кодом, предполагая, что пути источника и назначения имеют корень?
Хороший ответ, но одно замечание: разве имя переменной newPath не вводит в заблуждение? Разве его не следует называть скорее sourcePath или sourceFilePath?
Что, если нам нужны только файлы из каталогов и подкаталогов. и не хотите каталогов? Кто угодно ?
@ Вероятно, шансы на это невелики, но я все же согласен с тем, что подстрока - более правильный способ, чем вызывать замену
Давайте создадим что-нибудь и внесем это в Open Source .NET Core ...: /
Это не удается, если каталог содержит какую-либо соединительную ссылку или символическую ссылку. Есть ли способ преодолеть это?
Directory.GetDirectories("*.*", System.IO.SearchOption.AllDirectories) выйдет из строя, если какой-либо из подкаталогов в указанном корне вызовет DirectoryNotFoundException или UnauthorizedAccessException, весь метод завершится ошибкой и не вернет ни одного каталога. Можете ли вы предложить метод, который будет учитывать при возникновении упомянутых выше исключений?
Этот сайт всегда очень помогал мне, и теперь моя очередь помогать другим тем, что я знаю.
Надеюсь, что приведенный ниже код будет кому-то полезен.
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, вы правы. Я изменил код с твоим предложением.
замените 'file_name.Substring (..)' на 'Path.GetFileName (file_name)'
Я считаю, что замена @dgzornoza не сработает, потому что функции подстроки удаляют часть source_dir из имени файла, но сохраняют полный путь. Пример: вы копируете файл из D: \ sources \ install.wim в E: \. С функцией подстроки он станет E: \ sources \ install.wim, но если я использую GetFileName, это будет E: \ install.wim. С рекурсией дело обстоит еще хуже.
да, недопустимый GetFileName. Лучшее решение - Substring, заменить предыдущий ответ небезопасно. Спасибо
Лучший ответ, но вы должны использовать source_dir.Length + 1, как упоминалось, или TrimStart('\'))
Отлично. Первоначально мои папки создавались бы с отсутствующей первой буквой. Например: ongs вместо Songs. Затем я удалил косую черту в source_dir = @ "E: \"; и сделал это source_dir = @ "E:" ;. Тогда это сработало в мгновение ока !!!
Незначительное улучшение ответа 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));
}
}
Это мой код, надеюсь, это поможет
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 сейчас в перечислениях.
Вот метод расширения для 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;
}
Если вам нравится популярный ответ Конрада, но вы хотите, чтобы сам 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;
}
Версия tboswell replace Proof (устойчива к повторяющемуся шаблону в пути к файлу)
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(). Я не совсем понимаю, почему, но я предполагаю, что делал что-то, связанное с этим замечанием в документация, который рекомендует Path.Join()
Вы всегда можете использовать это, взятый с сайта Microsoft.
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); говорится: «скопируйте этот файл в это место, только если он не существует», что в большинстве случаев не то, что нам нужно. Но я могу понять, почему это по умолчанию. Может быть, добавить метку в метод перезаписи файлов.
Используйте этот класс.
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);.
Лучше, чем любой код (метод расширения 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;
}
}
Я не уверен, что это добавляет к принятому ответу, кроме использования рекурсии (где это не нужно) и сокрытия исключений, чтобы усложнить отладку.
Один вариант с одним циклом для копирования всех папок и файлов:
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() вне цикла, вместо того, чтобы полагаться на статический метод.
Возможно, он не учитывает производительность, но я использую его для папок размером 30 МБ, и он работает безупречно. К тому же мне не понравился весь объем кода и рекурсии, необходимый для такой простой задачи.
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-файл на диске, что является большой дополнительной работой для копии файла - вы не только создаете дополнительную копию данных, но и тратите время процессора на ее сжатие и распаковку. Я уверен, что это работает, так же, как вы, вероятно, можете забить гвоздь своей обувью, но это больше работы с большим количеством вещей, которые могут пойти не так, хотя есть более эффективные способы сделать это.
Причина, по которой я пришел к этому, - замена строк. Как отмечали другие, принятый ответ вызывает много опасений; ссылка соединения может не работать, а также повторять шаблон папки или файлы без расширения или имени. Меньше кода, меньше шансов ошибиться. А поскольку время процессора меня не беспокоит, оно подходит для моего конкретного случая.
Да, это все равно что проехать 1000 миль, чтобы избежать светофора, но это ваше путешествие, так что дерзайте. Проверка шаблонов папок тривиальна по сравнению с тем, что ZIP должен делать изнутри. Я настоятельно рекомендую этого не делать всем, кто заботится о том, чтобы не тратить впустую процессор, диск, электричество или где это необходимо, чтобы работать вместе с другими программами на той же машине. Кроме того, если вам когда-нибудь зададут такой вопрос на собеседовании никогда, скажите: «Мой код простой, поэтому мне наплевать на время процессора» - вы не получите работу.
Перешел на ответ предоставил @ justin-r. Тем не менее, я оставлю этот ответ как еще один способ сделать это.
Если папки находятся в разных сетевых папках и содержат много файлов, это, на мой взгляд, лучший вариант.
Очень креативно! С точки зрения правильности это, вероятно, должен быть ответ №1, я уверен, что он обрабатывает 1000 крайних случаев, которые упускают другие наивные ответы. В качестве альтернативы выполните robocopy.exe (если x-plat не проблема).
Мое решение в основном является модификацией ответа @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));
}
Скопируйте и замените все файлы в папке
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);
}
}
Приветствую вас за ответ, но я не уверен, что это добавляет. Также trycatchthrow бессмысленен.
Код ниже - это предложение Microsoft как копировать каталоги и его разделяет уважаемый @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 не выглядит таким уродливым.