Как удалить каталог с файлами только для чтения в C#?


мне нужно удалить каталог, содержащий файлы только для чтения. Какой подход лучше:

  • используя DirectoryInfo.Delete() или

  • ManagementObject.InvokeMethod("Delete")?

С DirectoryInfo.Delete(), Я должен вручную отключить атрибут только для чтения для каждого файла, но ManagementObject.InvokeMethod("Delete") не нужно. Есть ли ситуация, когда одно предпочтительнее другого?

пример кода (тест.txt только для чтения).

первый образом:

DirectoryInfo dir = new DirectoryInfo(@"C:UsersDavidDesktop");
dir.CreateSubdirectory("Test");

DirectoryInfo test = new DirectoryInfo(@"C:UsersDavidDesktopTest");
File.Copy(@"C:UsersDavidDesktoptest.txt", @"C:UsersDavidDesktopTesttest.txt");
File.SetAttributes(@"C:UsersDavidDesktopTesttest.txt", FileAttributes.Archive);
test.Delete(true);

второй вариант:

DirectoryInfo dir = new DirectoryInfo(@"C:UsersDavidDesktop");
dir.CreateSubdirectory("Test");

DirectoryInfo test = new DirectoryInfo(@"C:UsersDavidDesktopTest");
File.Copy(@"C:UsersDavidDesktoptest.txt", @"C:UsersDavidDesktopTesttest.txt");

string folder = @"C:UsersDavidDesktopTest";
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)
    {
    }
}
11 53

11 ответов:

вот метод расширения, который устанавливает Attributes to 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();
}

самый простой способ избежать рекурсивных вызовов-это использовать при получении 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") фактически удаляет каталог.

то, что мне не нравится в первом подходе (каталог.delete)-это случай, когда есть подкаталоги, которые также содержат файлы только для чтения, и у них есть подкаталоги, которые также имеют файлы только для чтения и т. д. Похоже, вам придется отключить этот флаг для каждого файла в директории и всех подкаталогов рекурсивно.

при втором подходе вы можете просто удалить этот первый каталог, и он не проверяет, доступны ли файлы только для чтения. Тем не менее, это первый раз, когда я использовал WMI в C#, так что я не совсем комфортно с ним. Поэтому я не уверен, когда следует использовать подход WMI для других приложений, а не просто использовать System.IO методы.

на первый взгляд, использование подхода 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();
  }