Как мне удалить каталог с файлами, доступными только для чтения в C #?
-
03-07-2019 - |
Вопрос
Мне нужно удалить каталог, содержащий файлы, доступные только для чтения.Какой подход лучше:
Используя
DirectoryInfo.Delete()
, или,ManagementObject.InvokeMethod("Delete")
?
С DirectoryInfo.Delete()
, мне приходится вручную отключать атрибут "только для чтения" для каждого файла, но ManagementObject.InvokeMethod("Delete")
похоже, в этом нет необходимости.Есть ли какая-нибудь ситуация, когда одно предпочтительнее другого?
Пример кода (test.txt доступен только для чтения).
Первый способ:
DirectoryInfo dir = new DirectoryInfo(@"C:\Users\David\Desktop\");
dir.CreateSubdirectory("Test");
DirectoryInfo test = new DirectoryInfo(@"C:\Users\David\Desktop\Test\");
File.Copy(@"C:\Users\David\Desktop\test.txt", @"C:\Users\David\Desktop\Test\test.txt");
File.SetAttributes(@"C:\Users\David\Desktop\Test\test.txt", FileAttributes.Archive);
test.Delete(true);
Второй способ:
DirectoryInfo dir = new DirectoryInfo(@"C:\Users\David\Desktop\");
dir.CreateSubdirectory("Test");
DirectoryInfo test = new DirectoryInfo(@"C:\Users\David\Desktop\Test\");
File.Copy(@"C:\Users\David\Desktop\test.txt", @"C:\Users\David\Desktop\Test\test.txt");
string folder = @"C:\Users\David\Desktop\Test";
string dirObject = "Win32_Directory.Name='" + folder + "'";
using (ManagementObject managementObject = new ManagementObject(dirObject))
{
managementObject.Get();
ManagementBaseObject outParams = managementObject.InvokeMethod("Delete", null,
null);
// ReturnValue should be 0, else failure
if (Convert.ToInt32(outParams.Properties["ReturnValue"].Value) != 0)
{
}
}
Решение
Вот метод расширения, который устанавливает Attributes
Для Normal
рекурсивно, затем удаляет элементы:
public static void DeleteReadOnly(this FileSystemInfo fileSystemInfo)
{
var directoryInfo = fileSystemInfo as DirectoryInfo;
if (directoryInfo != null)
{
foreach (FileSystemInfo childInfo in directoryInfo.GetFileSystemInfos())
{
childInfo.DeleteReadOnly();
}
}
fileSystemInfo.Attributes = FileAttributes.Normal;
fileSystemInfo.Delete();
}
Другие советы
Простейший способ избежать рекурсивных вызовов - это использовать AllDirectories
вариант при получении FileSystemInfo
s, вот так:
public static void ForceDeleteDirectory(string path)
{
var directory = new DirectoryInfo(path) { Attributes = FileAttributes.Normal };
foreach (var info in directory.GetFileSystemInfos("*", SearchOption.AllDirectories))
{
info.Attributes = FileAttributes.Normal;
}
directory.Delete(true);
}
Попробуй это,
private void DeleteRecursiveFolder(string pFolderPath)
{
foreach (string Folder in Directory.GetDirectories(pFolderPath))
{
DeleteRecursiveFolder(Folder);
}
foreach (string file in Directory.GetFiles(pFolderPath))
{
var pPath = Path.Combine(pFolderPath, file);
FileInfo fi = new FileInfo(pPath);
File.SetAttributes(pPath, FileAttributes.Normal);
File.Delete(file);
}
Directory.Delete(pFolderPath);
}
Другой метод без необходимости рекурсии.
public static void ForceDeleteDirectory(string path)
{
DirectoryInfo root;
Stack<DirectoryInfo> fols;
DirectoryInfo fol;
fols = new Stack<DirectoryInfo>();
root = new DirectoryInfo(path);
fols.Push(root);
while (fols.Count > 0)
{
fol = fols.Pop();
fol.Attributes = fol.Attributes & ~(FileAttributes.Archive | FileAttributes.ReadOnly | FileAttributes.Hidden);
foreach (DirectoryInfo d in fol.GetDirectories())
{
fols.Push(d);
}
foreach (FileInfo f in fol.GetFiles())
{
f.Attributes = f.Attributes & ~(FileAttributes.Archive | FileAttributes.ReadOnly | FileAttributes.Hidden);
f.Delete();
}
}
root.Delete(true);
}
private void DeleteRecursiveFolder(DirectoryInfo dirInfo)
{
foreach (var subDir in dirInfo.GetDirectories())
{
DeleteRecursiveFolder(subDir);
}
foreach (var file in dirInfo.GetFiles())
{
file.Attributes=FileAttributes.Normal;
file.Delete();
}
dirInfo.Delete();
}
Лучшее решение - пометить все файлы как недоступные только для чтения, а затем удалить каталог.
// delete/clear hidden attribute
File.SetAttributes(filePath, File.GetAttributes(filePath) & ~FileAttributes.Hidden);
// delete/clear archive and read only attributes
File.SetAttributes(filePath, File.GetAttributes(filePath)
& ~(FileAttributes.Archive | FileAttributes.ReadOnly));
Обратите внимание, что ~ - это побитовый логический оператор, который возвращает дополнение к заданному двоичному значению.Я не тестировал это, но это должно сработать.
Спасибо!
Я бы сказал, что ваш первый подход выглядит более явным и читабельным.Второй метод пахнет отражением, не является типобезопасным и выглядит странно.В ManagementObject
может представлять несколько вещей, поэтому не очевидно, что .InvokeMethod("Delete")
фактически удаляет каталог.
Что мне не нравится в первом подходе (directory.delete), так это тот случай, когда есть подкаталоги, которые также содержат файлы, доступные только для чтения, и у них есть подкаталоги, в которых также есть файлы, доступные только для чтения, и так далее.Похоже, вам пришлось бы рекурсивно отключить этот флаг для каждого файла в каталоге и всех подкаталогов.
При втором подходе вы можете просто удалить этот первый каталог, и он не проверяет, доступны ли файлы только для чтения.Однако это первый раз, когда я использую WMI в C #, поэтому мне это не очень нравится.Поэтому я не уверен, когда использовать подход WMI для других приложений, вместо того чтобы просто использовать System.Методы ввода-вывода.
На первый взгляд, использование подхода WMI кажется более эффективным, чем перебор всей файловой системы (предположим, например, что каталог содержит 10 тысяч файлов).Но я не знаю, что WMI также не выполняет итерации.Если это так, то, находясь ближе к металлу (опять же, предположения), это должно быть более эффективно.
Для элегантности я признаю, что рекурсивный метод - это круто.
Тестирование производительности должно ответить на вопрос об эффективности.И любой из них может быть элегантным, если обернут в метод расширения DirectoryInfo.
Вот еще одно решение, которое позволяет избежать рекурсии на себя.
public static void DirectoryDeleteAll(string directoryPath)
{
var rootInfo = new DirectoryInfo(directoryPath) { Attributes = FileAttributes.Normal };
foreach (var fileInfo in rootInfo.GetFileSystemInfos()) fileInfo.Attributes = FileAttributes.Normal;
foreach (var subDirectory in Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories))
{
var subInfo = new DirectoryInfo(subDirectory) { Attributes = FileAttributes.Normal };
foreach (var fileInfo in subInfo.GetFileSystemInfos()) fileInfo.Attributes = FileAttributes.Normal;
}
Directory.Delete(directoryPath, true);
}
Это работает путем сброса атрибутов в папках и файлах перед удалением, поэтому вы могли бы просто удалить последнюю строку для метода 'DirectoryResetAttributes' и использовать delete отдельно.
В связи с этим, хотя это работало, затем у меня возникли проблемы с удалением путей, которые были "слишком длинными", и в итоге я использовал решение robocopy, опубликованное здесь: C # удаление папки с длинными путями
Чтобы продолжить решение Виталия Улантикова, я дополнил его методом переименования / перемещения папки:
public static void renameFolder(String sourcePath, String targetPath) {
try
{
if (System.IO.Directory.Exists(targetPath))
DeleteFileSystemInfo(new DirectoryInfo(targetPath));
System.IO.Directory.Move(sourcePath, targetPath);
}
catch (Exception ex)
{
Console.WriteLine("renameFolder: " + sourcePath + " " + targetPath + " " + ex.Message);
throw ex;
}
}
private static void DeleteFileSystemInfo(FileSystemInfo fsi) {
fsi.Attributes = FileAttributes.Normal;
var di = fsi as DirectoryInfo;
if (di != null)
{
foreach (var dirInfo in di.GetFileSystemInfos())
{
DeleteFileSystemInfo(dirInfo);
}
}
fsi.Delete();
}