Share via


Procedura: scorrere una struttura ad albero di directory (Guida per programmatori C#)

Per "scorrere una struttura ad albero di directory" si intende accedere a ogni file in ogni sottodirectory annidata sotto una specifica cartella radice, a qualsiasi profondità. Non si deve necessariamente aprire ogni file. È possibile recuperare solo il nome del file o della sottodirectory come string o recuperare informazioni aggiuntive nella forma di oggetto System.IO.FileInfo o System.IO.DirectoryInfo.

Nota

In Windows, i termini "directory" e "cartella" sono utilizzati indifferentemente. Nella maggior parte della documentazione e del testo dell'interfaccia utente si utilizza il termine "cartella", ma nella libreria di classi .NET Framework si utilizza il termine "directory".

Nel caso più semplice, nel quale si è certi di disporre delle autorizzazioni di accesso per tutte le directory sotto una radice specificata, è possibile utilizzare il flag System.IO.SearchOption.AllDirectories. Questo flag restituisce tutte le sottodirectory annidate che corrispondono al modello specificato. Nell'esempio seguente viene illustrato come utilizzare questo flag.

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

La debolezza di questo approccio sta nel fatto che, se una delle sottodirectory sotto la radice specificata provoca DirectoryNotFoundException o UnauthorizedAccessException, l'esecuzione dell'intero metodo ha esito negativo e non viene restituita alcuna directory. La stessa situazione può verificarsi quando si utilizza il metodo GetFiles. Se è necessario gestire queste eccezioni sulle sottocartelle specifiche, occorre procedere manualmente nella struttura ad albero di directory, come mostrato negli esempi seguenti.

Quando si procede manualmente in una struttura ad albero di directory, è possibile gestire prima le sottodirectory (attraversamento pre-ordine) o prima i file (attraversamento post-ordine). Se si esegue un attraversamento pre-ordine, si procede lungo l'intera struttura ad albero sotto la cartella corrente prima di scorrere i file che sono direttamente in quella cartella. Negli esempi disponibili più avanti in questo documento viene eseguito l'attraversamento post-ordine, ma è possibile modificare tali esempi facilmente per eseguire l'attraversamento pre-ordine.

Un'altra opzione consiste nel decidere se utilizzare un attraversamento ricorsivo o basato su stack. Negli esempi disponibili più avanti in questo documento sono mostrati entrambi gli approcci.

Se è necessario eseguire svariate operazioni su file e cartelle, è possibile modularizzare questi esempi effettuando il refactoring dell'operazione in funzioni separate che è possibile richiamare tramite un solo delegato.

Nota

I file system NTFS possono contenere punti di analisi nella forma di punti di giunzione, collegamenti simbolici e collegamenti reali. I metodi .NET Framework come GetFiles e GetDirectories non restituiranno le sottodirectory sotto un punto di analisi. Questo comportamento protegge dal rischio di entrare in un ciclo infinito quando due punti di analisi si riferiscono l'uno all'altro. In generale, è necessario prestare estrema attenzione nella gestione dei punti di analisi per assicurarsi di non modificare o eliminare file in modo accidentale. Se si richiede un controllo preciso sui punti di analisi, utilizzare pInvoke o codice nativo per chiamare direttamente i metodi appropriati del file system Win32.

Esempio

Nell'esempio seguente viene mostrato come procedere in una struttura ad albero di directory tramite ricorsione. L'approccio ricorsivo è elegante ma può potenzialmente provocare un'eccezione di overflow dello stack se la struttura ad albero di directory è ampia e annidata in modo profondo.

Le specifiche eccezioni gestite e le specifiche azioni eseguite su ogni file o cartella vengono fornite solo come esempi. Questo codice va modificato per soddisfare requisiti specifici. Per ulteriori informazioni, vedere i commenti nel codice.

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

Nell'esempio seguente viene mostrato come scorrere file e cartelle in una struttura ad albero di directory senza utilizzare la ricorsione. Questa tecnica utilizza il tipo di insieme Stack<T> generico, che è uno stack LIFO (Last In, First Out).

Le specifiche eccezioni gestite e le specifiche azioni eseguite su ogni file o cartella vengono fornite solo come esempi. Questo codice va modificato per soddisfare requisiti specifici. Per ulteriori informazioni, vedere i commenti nel codice.

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

L'operazione di verifica su ogni cartella per determinare se l'applicazione dispone dell'autorizzazione ad aprirla richiede in genere troppo tempo. L'esempio di codice, pertanto, include solo quella parte dell'operazione in un blocco try/catch. È possibile modificare il blocco catch in modo da tentare di elevare le autorizzazioni e provare nuovamente l'accesso in caso di accesso negato a una cartella. Di regola, rilevare solo le eccezioni che è possibile gestire senza lasciare l'applicazione in stato sconosciuto.

Se è necessario archiviare il contenuto di una struttura ad albero di directory, in memoria o su disco, la migliore opzione è archiviare solo la proprietà FullName (di tipo string) per ogni file. È possibile utilizzare quindi questa stringa per creare un nuovo oggetto FileInfo o DirectoryInfo, in base alle esigenze, o aprire i file che richiedono ulteriore elaborazione.

Programmazione efficiente

Un codice di iterazione di file efficiente deve tenere in considerazione le molte complessità del file system. Per ulteriori informazioni, vedere NTFS Technical Reference (informazioni in lingua inglese).

Vedere anche

Riferimenti

System.IO

Concetti

Directory di file e LINQ

Altre risorse

File system e Registro di sistema (Guida per programmatori C#)