Gewusst wie: Durchlaufen einer Verzeichnisstruktur (C#-Programmierhandbuch)

Der Ausdruck "Durchlaufen einer Verzeichnisstruktur" bedeutet, auf jede Datei in einem verschachtelten Unterverzeichnis in einem angegebenen Stammordner zuzugreifen. Sie müssen nicht unbedingt jede Datei öffnen. Sie können einfach den Namen der Datei oder des Unterverzeichnisses als string oder zusätzliche Informationen in Form eines System.IO.FileInfo- Objekts oder System.IO.DirectoryInfo-Objekts abrufen.

Tipp

In Windows sind die Begriffe "Verzeichnis" und "Ordner" austauschbar. In den meisten Dokumentationen und Benutzeroberflächentexten wird der Begriff "Ordner" verwendet, die .NET Framework-Klassenbibliothek verwendet jedoch den Begriff "Verzeichnis".

Im einfachsten Fall, bei dem Sie ganz sicher sind, dass Sie Zugriffsberechtigungen für alle Verzeichnisse in einem bestimmten Stammverzeichnis haben, können Sie das System.IO.SearchOption.AllDirectories-Flag verwenden. Dieses Flag gibt alle geschachtelten Unterverzeichnisse zurück, die mit dem angegebenen Muster übereinstimmen. Im folgenden Beispiel wird die Verwendung dieses Flags dargestellt.

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

Der Schwachpunkt bei dieser Methode besteht darin, dass die Methode fehlschlägt und keine Verzeichnisse zurückgibt, falls eines der Unterverzeichnisse im angegebenen Stammverzeichnis DirectoryNotFoundException oder UnauthorizedAccessException auslöst. Dasselbe gilt, wenn Sie die GetFiles-Methode verwenden. Wenn Sie diese Ausnahmen für bestimmte Unterverzeichnisse umgehen müssen, müssen Sie die Verzeichnisstruktur manuell durchlaufen, wie in den folgenden Beispielen dargestellt.

Wenn Sie eine Verzeichnisstruktur manuell durchlaufen, können Sie zuerst die Unterverzeichnisse (Durchlauf vor der Sortierung) oder zuerst die Dateien (Durchlauf nach der Sortierung) durchlaufen. Wenn Sie einen Durchlauf vor der Sortierung ausführen, durchlaufen Sie die gesamte Verzeichnisstruktur unter dem aktuellen Verzeichnis vor den Dateien, die sich direkt im diesem Verzeichnis befinden. In den Beispielen weiter unten in diesem Dokument wird ein Durchlauf nach der Sortierung ausgeführt. Sie können diesen aber problemlos in einen Durchlauf vor der Sortierung ändern.

Außerdem können Sie zwischen einem Rekursions- und einem stapelbasierten Durchlauf wählen. In den Beispielen weiter unten in diesem Dokument werden beide Verfahren erläutert.

Falls Sie verschiedene Operationen für Dateien und Ordner durchführen müssen, können Sie diese Beispiele in einer modularen Struktur halten. Gestalten Sie die Operation dazu in separate Funktionen um, die Sie mit einem einzelnen Delegaten aufrufen können.

Tipp

NTFS-Dateisysteme können Reparse Points in Form von Abzweigungspunkten, symbolischen Links und festen Links enthalten. Die .NET Framework-Methoden, wie GetFiles und GetDirectories geben keine Unterverzeichnisse unterhalb eines Analysepunkts zurück. Dieses Verhalten schützt vor dem Risiko einer Endlosschleife, wenn zwei Reparse Points aufeinander verweisen. Im Allgemeinen sollten Sie extrem vorsichtig bei der Verwendung von Reparse Points sein, um sicherzustellen, dass Sie nicht unbeabsichtigterweise Dateien ändern oder löschen. Wenn Sie Reparse Points genau steuern möchten, verwenden Sie den Plattformaufruf oder systemeigenen Code zum direkten Aufrufen der entsprechenden Win32-Dateisystemmethoden.

Beispiel

Im folgenden Beispiel wird dargestellt, wie eine Verzeichnisstruktur mit Rekursion durchlaufen wird. Der rekursive Ansatz ist elegant. Jedoch besteht die Gefahr, dass eine Stapelüberlaufausnahme verursacht wird, falls es sich um eine große und tief verschachtelte Verzeichnisstruktur handelt.

Die besonderen Ausnahmen, die für jede Datei und jeden Ordner behandelt werden, und die besonderen Aktionen, die für jede Datei und jeden Ordner ausgeführt werden, werden lediglich als Beispiele dargestellt. Sie sollten diesen Code ändern, um Ihre speziellen Anforderungen zu erfüllen. Weitere Informationen finden Sie in den Kommentaren im Code.

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

Im folgenden Beispiel wird dargestellt, wie Verzeichnisse und Ordner in einer Verzeichnisstruktur ohne Rekursion durchlaufen werden. Bei dieser Methode wird der generische Stack<T>-Auflistungstyp verwendet, bei dem es sich um einen LIFO-Stapel handelt.

Die besonderen Ausnahmen für jede Datei und jeden Ordner und die besonderen Aktionen, die für jede Datei und jeden Ordner ausgeführt werden, werden lediglich als Beispiel dargestellt. Sie sollten diesen Code ändern, um Ihre speziellen Anforderungen zu erfüllen. Weitere Informationen finden Sie in den Kommentaren im Code.

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

In der Regel ist es zu zeitaufwändig, jeden Ordner zu testen, um zu ermitteln, ob Ihre Anwendung über die Berechtigungen zum Öffnen verfügt. Im Codebeispiel wird deshalb nur dieser Teil der Operation in einen try/catch-Block eingeschlossen. Sie können den catch-Block so ändern, dass Ihre Berechtigungen bei verweigertem Zugriff auf einen Ordner erhöht werden. Anschließend können Sie erneut darauf zugreifen. Fangen Sie als Regel einfach die Ausnahmen ab, die Sie verarbeiten können, ohne Ihre Anwendung in einem unbekannten Zustand zu lassen.

Falls Sie den Inhalt einer Verzeichnisstruktur entweder im Arbeitsspeicher oder auf Festplatte speichern müssen, speichern Sie am besten nur die FullName-Eigenschaft (vom Typ string) für jede Datei. Sie können diese Zeichenfolge dann verwenden, um ggf. ein neues FileInfo-Objekt oder DirectoryInfo-Objekt zu erstellen, oder eine Datei öffnen, für die eine weitere Bearbeitung erforderlich ist.

Robuste Programmierung

Beim robusten Dateiiterationscode müssen viele komplexe Zusammenhänge des Dateisystems berücksichtigt werden. Weitere Informationen finden Sie in der NTFS Technical Reference.

Siehe auch

Referenz

System.IO

Konzepte

LINQ und Dateiverzeichnisse

Weitere Ressourcen

Das Dateisystem und die Registrierung (C#-Programmierhandbuch)