Pregunta

Necesito eliminar un directorio que contiene archivos de solo lectura. ¿Qué enfoque es mejor:

  • Utilizando DirectoryInfo.Delete () , o,

  • ManagementObject.InvokeMethod (" Delete ") ?

Con DirectoryInfo.Delete () , tengo que desactivar manualmente el atributo de solo lectura para cada archivo, pero ManagementObject.InvokeMethod (" Delete ") doesn Parece que no es necesario. ¿Hay alguna situación en la que uno sea más preferible que el otro?

Código de muestra (test.txt es de solo lectura).

Primera forma:

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

Segunda vía:

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)
    {
    }
}
¿Fue útil?

Solución

Aquí hay un método de extensión que establece Attributes en Normal recursivamente, luego borra los elementos:

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

Otros consejos

La forma más sencilla de evitar llamadas recursivas es utilizar la opción AllDirectories al obtener FileSystemInfo , como así:

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

Prueba esto,

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

Otro método sin la necesidad de recursión.

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

La mejor solución es marcar todos los archivos como no solo de lectura y luego eliminar el directorio.

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

Observe que ~ es un operador lógico de Bitwise que devuelve el complemento del valor binario dado. No he probado esto, pero debería funcionar.

¡Gracias!

Yo diría que su primer enfoque parece más explícito y legible. El segundo método huele a reflexión, no es seguro y se ve raro. El ManagementObject puede representar varias cosas, por lo que no es obvio que .InvokeMethod (" Delete ") en realidad elimine un directorio.

Lo que no me gusta de la primera aproximación (directory.delete) es el caso donde hay subdirectorios que también contienen archivos de solo lectura, y tienen subdirectorios que también tienen archivos de solo lectura, y así en. Parece que tendrías que desactivar esa bandera para cada archivo en el directorio y todos los subdirectorios recursivamente.

Con el segundo enfoque, puedes eliminar ese primer directorio y no se comprueba si los archivos son de solo lectura. Sin embargo, esta es la primera vez que uso WMI en C #, por lo que no me siento tan cómodo con él. Por lo tanto, no estoy seguro de cuándo utilizar el enfoque de WMI para otras aplicaciones, en lugar de utilizar los métodos System.IO.

En la superficie, el uso del enfoque WMI parece más eficiente que la iteración en todo el sistema de archivos (supongamos, por ejemplo, que el directorio tiene 10 de miles de archivos). Pero no sé que WMI tampoco hace iteraciones. Si lo hace, al estar más cerca del metal (nuevamente, suposiciones) debería ser más eficiente.

Por elegancia, concedo que el método recursivo es genial.

Las pruebas de rendimiento deben responder a la pregunta de eficiencia. Y cualquiera de los dos puede ser elegante si está envuelto en un método de extensión de DirectoryInfo.

Aquí hay otra solución que evita la recursión en sí misma.

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

Esto funciona al restablecer los atributos en las carpetas y archivos antes de la eliminación, por lo que solo puede eliminar la última línea del método 'DirectoryResetAttributes' y usar la eliminación por separado.

En una nota relacionada, mientras esto funcionaba, tuve problemas para eliminar rutas que eran 'demasiado largas' y terminé usando una solución robocopy publicada aquí: C # eliminando una carpeta que tiene rutas largas

Para hacer un seguimiento de la solución de Vitaliy Ulantikov, la he complementado con un método de renombrar / mover carpetas:

  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();
  }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top