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

Der Ausdruck „Durchlaufen einer Verzeichnisstruktur“ bedeutet, dass auf jede Datei in jedem verschachtelten Unterverzeichnis in einem angegebenen Stammordner in einer beliebigen Tiefe zugegriffen wird. Sie müssen nicht unbedingt jede Datei öffnen. Sie können einfach den Namen der Datei oder dem Unterverzeichnis als string abrufen, oder Sie können zusätzliche Informationen eines <xref:System.IO.FileInfo?displayProperty=fullName> oder <xref:System.IO.DirectoryInfo?displayProperty=fullName>-Objekts abrufen.

Hinweis

In Windows sind die Begriffe „Verzeichnis“ und „Ordner“ austauschbar. Die meisten Dokumentationen und der Text der Benutzeroberfläche verwenden den Begriff „Ordner“, aber die .NET Framework-Klassenbibliothek verwendet den Begriff „Verzeichnis“.

Im einfachsten Fall, in dem Sie ganz sicher sind, dass Sie über die Zugriffsberechtigungen für alle Verzeichnisse in einem angegebenen Stamm verfügen, 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 veranschaulicht.

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

Der Schwachpunkt bei diesem Ansatz ist, dass die Methode fehlschlägt und keine Verzeichnisse zurückgibt, wenn eines der Unterverzeichnisse im angegebenen Stamm eine <xref:System.IO.DirectoryNotFoundException> oder <xref:System.UnauthorizedAccessException> bewirkt. Dasselbe gilt bei Verwendung der <xref:System.IO.DirectoryInfo.GetFiles%2A>-Methode. Wenn Sie diese Ausnahmen für bestimmte Unterverzeichnisse behandeln müssen, müssen Sie die Verzeichnisstruktur manuell durchlaufen, wie in den folgenden Beispielen gezeigt.

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

Eine andere Option ist, entweder Rekursion oder einen stapelbasierten Durchlauf zu verwenden. In den Beispielen weiter unten in diesem Dokument werden beide Ansätze gezeigt.

Wenn Sie mehrere Vorgänge für Dateien und Ordner ausführen müssen, können Sie diese Beispiele modularisieren, indem Sie den Vorgang in separate Funktionen umgestalten, die Sie mit einem einzelnen Delegaten aufrufen können.

Hinweis

NTFS-Dateisysteme können Analysepunkte in Form von Verknüpfungspunkten, symbolischen Verknüpfungen und festen Links enthalten. .NET Framework-Methoden wie z.B. <xref:System.IO.DirectoryInfo.GetFiles%2A> und <xref:System.IO.DirectoryInfo.GetDirectories%2A> geben keine Unterverzeichnisse unter einem Analysepunkt zurück. Dieses Verhalten schützt vor dem Risiko einer Endlosschleife, wenn zwei Analysepunkte aufeinander verweisen. Im Allgemeinen sollten Sie bei Analysepunkten äußerst vorsichtig sein, um sicherzustellen, dass Sie nicht versehentlich Dateien ändern oder löschen. Wenn Sie genaue Kontrolle über Analysepunkte benötigen, verwenden Sie einen Plattformaufruf oder nativen Code, um die entsprechenden Win32-Dateisystemmethoden direkt aufzurufen.

Beispiel

Das folgende Beispiel zeigt, wie Sie eine Verzeichnisstruktur mit Rekursion direkt durchlaufen. Der rekursive Ansatz ist elegant, kann aber eine Stapelüberlaufausnahme verursachen, wenn die Verzeichnisstruktur groß und tief verschachtelt ist.

Die besonderen Ausnahmen, die verarbeitet werden, und die besonderen Aktionen, die für jede Datei und jeden Ordner ausgeführt werden, werden nur als Beispiele angegeben. Sie sollten diesen Code für Ihre speziellen Anforderungen ändern. 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);
            }
        }            
    }
}

Beispiel

Im folgenden Beispiel wird gezeigt, wie Sie Dateien und Ordner in einer Verzeichnisstruktur ohne Rekursion durchlaufen. Diese Technik verwendet den generischen <xref:System.Collections.Generic.Stack%601>-Auflistungstyp, bei dem es sich um einen LIFO-Stapel handelt.

Die besonderen Ausnahmen, die verarbeitet werden, und die besonderen Aktionen, die für jede Datei und jeden Ordner ausgeführt werden, werden nur als Beispiele angegeben. Sie sollten diesen Code für Ihre speziellen Anforderungen ändern. 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);
        }
    }
}

Es ist im Allgemeinen zu zeitaufwändig, bei allen Ordnern zu testen, ob die Anwendung über die Berechtigung zum Öffnen verfügt. Im Codebeispiel wird daher nur dieser Teil der Operation in einen try/catch-Block eingeschlossen. Sie können den catch-Block ändern, sodass Sie bei verweigertem Zugriff auf einen Ordner versuchen, Ihre Berechtigungen zu erhöhen, und dann erneut darauf zugreifen. In der Regel sollten Sie nur die Ausnahmen abfangen, die Sie behandeln können, ohne Ihre Anwendung in einem unbekannten Status zu lassen.

Wenn Sie den Inhalt einer Verzeichnisstruktur entweder im Arbeitsspeicher oder auf dem Datenträger speichern müssen, speichern Sie am besten nur die <xref:System.IO.FileSystemInfo.FullName%2A>-Eigenschaft (vom Typ string) für jede Datei. Anschließend können Sie diese Zeichenfolge nach Bedarf zum Erstellen eines neuen <xref:System.IO.FileInfo>- oder <xref:System.IO.DirectoryInfo>-Objekts verwenden, oder eine beliebige Datei öffnen, für die zusätzliche Verarbeitung erforderlich ist.

Stabile Programmierung

Bei stabilem Dateiiterationscode müssen viele komplexe Zusammenhänge des Dateisystems berücksichtigt werden. Weitere Informationen finden Sie unter NTFS Technical Reference (Technische Referenz für NTFS).

Siehe auch

<xref:System.IO>
LINQ und Dateiverzeichnisse
Das Dateisystem und die Registrierung (C#-Programmierhandbuch)