Compartir a través de


Cómo: Recorrer en iteración un árbol de directorio (Guía de programación de C#)

La frase "recorrer en iteración un árbol de directorios" significa obtener acceso a cada uno de los archivos de todos los subdirectorios anidados bajo una carpeta raíz especificada hasta un nivel de profundidad cualquiera. No es necesario abrir cada archivo. Puede limitarse a recuperar el nombre del archivo o subdirectorio como string, o bien recuperar información adicional en forma de objeto DirectoryInfo o FileInfo.

NotaNota

En Windows, los términos "directorio" y "carpeta" se utilizan indistintamente.La mayor parte de la documentación y del texto de la interfaz de usuario utiliza el término "carpeta", pero la biblioteca de clases de .NET Framework utiliza el término "directorio".

En el caso más simple, en el que sabe con seguridad que tiene permisos de acceso para todos los directorios incluidos en una raíz especificada, puede utilizar el marcador System.IO.SearchOption.AllDirectories. Este marcador devuelve todos los subdirectorios anidados que coinciden con el modelo especificado. En el ejemplo siguiente se muestra cómo utilizar este marcador.

root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);

El punto débil de este enfoque es que si uno de los subdirectorios incluidos en la raíz especificada produce una excepción DirectoryNotFoundException o UnauthorizedAccessException, se produce un error de todo el método y no devuelve ningún directorio. Lo mismo sucede al utilizar el método GetFiles. Si tiene que controlar estas excepciones en subcarpetas específicas, debe recorrer manualmente el árbol de directorios, como se muestra en los ejemplos siguientes.

Al recorrer manualmente un árbol de directorios, puede controlar primero los subdirectorios (recorrido en preorden) o los archivos (recorrido en postorden). Al realizar un recorrido en preorden, se recorre todo el árbol bajo la carpeta actual antes de recorrer en iteración los archivos que están directamente en esa carpeta. Los ejemplos que se proporcionan más adelante en este documento realizan un recorrido en postorden, si bien puede modificarlos fácilmente para que realicen un recorrido en preorden.

Otra opción consiste en utilizar la recursividad o un recorrido basado en la pila. Los ejemplos que se proporcionan más adelante en este documento muestran ambos enfoques.

Si tiene que realizar diversas operaciones en los archivos y las carpetas, puede dividir estos ejemplos en partes mediante la refactorización de la operación en funciones separadas que se puedan invocar utilizando un solo delegado.

NotaNota

Los sistemas de archivos NTFS pueden contener puntos de reanálisis en forma de puntos de unión, vínculos simbólicos y vínculos físicos.Los métodos de .NET Framework como GetFiles y GetDirectories no devolverán ningún subdirectorio situado bajo un punto de reanálisis.Este comportamiento protege frente al riesgo de provocar un bucle infinito cuando dos puntos de reanálisis se hacen referencia entre sí.En general, debería ser muy cuidadoso al tratar con puntos de reanálisis para asegurarse de no modificar o eliminar archivos involuntariamente.Si desea obtener un control preciso de los puntos de reanálisis, utilice la invocación de plataforma o código nativo para llamar directamente a los métodos de sistema de archivos Win32 adecuados.

Ejemplo

En el ejemplo siguiente se muestra cómo recorrer un árbol de directorios mediante recursividad. El enfoque recursivo resulta elegante, pero puede producir una excepción de desbordamiento de la pila si el árbol de directorios es grande y cuenta con muchos elementos anidados.

Las excepciones concretas que se controlan y las acciones determinadas que se realizan en cada archivo o carpeta se proporcionan simplemente como ejemplos. Debe modificar este código para que se ajuste a sus requisitos concretos. Para obtener más información, vea los comentarios del código.

public class RecursiveFileSearch
{
    static System.Collections.Specialized.StringCollection log = new System.Collections.Specialized.StringCollection();

    static void Main()
    {
        // Start with drives if you have to search the entire computer. 
        string[] drives = System.Environment.GetLogicalDrives();

        foreach (string dr in drives)
        {
            System.IO.DriveInfo di = new System.IO.DriveInfo(dr);

            // Here we skip the drive if it is not ready to be read. This 
            // is not necessarily the appropriate action in all scenarios. 
            if (!di.IsReady)
            {
                Console.WriteLine("The drive {0} could not be read", di.Name);
                continue;
            }
            System.IO.DirectoryInfo rootDir = di.RootDirectory;
            WalkDirectoryTree(rootDir);
        }

        // Write out all the files that could not be processed.
        Console.WriteLine("Files with restricted access:");
        foreach (string s in log)
        {
            Console.WriteLine(s);
        }
        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key");
        Console.ReadKey();
    }

    static void WalkDirectoryTree(System.IO.DirectoryInfo root)
    {
        System.IO.FileInfo[] files = null;
        System.IO.DirectoryInfo[] subDirs = null;

        // First, process all the files directly under this folder 
        try
        {
            files = root.GetFiles("*.*");
        }
        // This is thrown if even one of the files requires permissions greater 
        // than the application provides. 
        catch (UnauthorizedAccessException e)
        {
            // This code just writes out the message and continues to recurse. 
            // You may decide to do something different here. For example, you 
            // can try to elevate your privileges and access the file again.
            log.Add(e.Message);
        }

        catch (System.IO.DirectoryNotFoundException e)
        {
            Console.WriteLine(e.Message);
        }

        if (files != null)
        {
            foreach (System.IO.FileInfo fi in files)
            {
                // In this example, we only access the existing FileInfo object. If we 
                // want to open, delete or modify the file, then 
                // a try-catch block is required here to handle the case 
                // where the file has been deleted since the call to TraverseTree().
                Console.WriteLine(fi.FullName);
            }

            // Now find all the subdirectories under this directory.
            subDirs = root.GetDirectories();

            foreach (System.IO.DirectoryInfo dirInfo in subDirs)
            {
                // Resursive call for each subdirectory.
                WalkDirectoryTree(dirInfo);
            }
        }            
    }
}

En el ejemplo siguiente se muestra cómo recorrer en iteración los archivos y las carpetas de un árbol de directorios sin utilizar la recursividad. Esta técnica utiliza el tipo de colección genérica Stack, que es una pila de tipo LIFO (último en entrar, primero en salir).

Las excepciones concretas que se controlan y las acciones determinadas que se realizan en cada archivo o carpeta se proporcionan simplemente como ejemplos. Debe modificar este código para que se ajuste a sus requisitos concretos. Para obtener más información, vea los comentarios del código.

public class StackBasedIteration
{
    static void Main(string[] args)
    {
        // Specify the starting folder on the command line, or in  
        // Visual Studio in the Project > Properties > Debug pane.
        TraverseTree(args[0]);

        Console.WriteLine("Press any key");
        Console.ReadKey();
    }

    public static void TraverseTree(string root)
    {
        // Data structure to hold names of subfolders to be 
        // examined for files.
        Stack<string> dirs = new Stack<string>(20);

        if (!System.IO.Directory.Exists(root))
        {
            throw new ArgumentException();
        }
        dirs.Push(root);

        while (dirs.Count > 0)
        {
            string currentDir = dirs.Pop();
            string[] subDirs;
            try
            {
                subDirs = System.IO.Directory.GetDirectories(currentDir);
            }
            // An UnauthorizedAccessException exception will be thrown if we do not have 
            // discovery permission on a folder or file. It may or may not be acceptable  
            // to ignore the exception and continue enumerating the remaining files and  
            // folders. It is also possible (but unlikely) that a DirectoryNotFound exception  
            // will be raised. This will happen if currentDir has been deleted by 
            // another application or thread after our call to Directory.Exists. The  
            // choice of which exceptions to catch depends entirely on the specific task  
            // you are intending to perform and also on how much you know with certainty  
            // about the systems on which this code will run. 
            catch (UnauthorizedAccessException e)
            {                    
                Console.WriteLine(e.Message);
                continue;
            }
            catch (System.IO.DirectoryNotFoundException e)
            {
                Console.WriteLine(e.Message);
                continue;
            }

            string[] files = null;
            try
            {
                files = System.IO.Directory.GetFiles(currentDir);
            }

            catch (UnauthorizedAccessException e)
            {

                Console.WriteLine(e.Message);
                continue;
            }

            catch (System.IO.DirectoryNotFoundException e)
            {
                Console.WriteLine(e.Message);
                continue;
            }
            // Perform the required action on each file here. 
            // Modify this block to perform your required task. 
            foreach (string file in files)
            {
                try
                {
                    // Perform whatever action is required in your scenario.
                    System.IO.FileInfo fi = new System.IO.FileInfo(file);
                    Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
                }
                catch (System.IO.FileNotFoundException e)
                {
                    // If file was deleted by a separate application 
                    //  or thread since the call to TraverseTree() 
                    // then just continue.
                    Console.WriteLine(e.Message);
                    continue;
                }
            }

            // Push the subdirectories onto the stack for traversal. 
            // This could also be done before handing the files. 
            foreach (string str in subDirs)
                dirs.Push(str);
        }
    }
}

Generalmente se tarda mucho tiempo en comprobar cada carpeta para determinar si su aplicación tiene permiso para abrirla. Por consiguiente, el ejemplo de código incluye esa parte de la operación en un bloque try/catch. Puede modificar el bloque catch de forma que, cuando se le deniegue el acceso a una carpeta, intente elevar sus permisos y obtener acceso a ésta de nuevo. Como norma, detecte solamente las excepciones que puede controlar sin dejar la aplicación en un estado desconocido.

Si debe almacenar el contenido de un árbol de directorios, ya sea en memoria o en el disco, la mejor opción es almacenar solamente la propiedad FullName (de tipo string) para cada archivo. Después, puede utilizar esta cadena para crear un nuevo objeto DirectoryInfo o FileInfo, según sea necesario, o para abrir cualquier archivo que requiera un procesamiento adicional.

Programación eficaz

Un código eficaz de iteración de archivos debe tener en cuenta las numerosas dificultades del sistema de archivos. Para obtener más información, vea NTFS Technical Reference.

Vea también

Referencia

System.IO

Conceptos

LINQ y directorios de archivos

Otros recursos

Registro y sistema de archivos (Guía de programación de C#)