9 Gäste und 0 Benutzer online | Anmelden | Registrieren


Designelement Startseite

Designelement Windows-Artikel
  Designelement Problemlösungen
  Designelement Einstellungen
  Designelement Anleitungen
  Designelement Hardware
  Designelement FAQ
  Designelement C#.NET

Designelement Forum
Designelement Gästebuch

Designelement Programme
  Designelement Onlinetools
  Designelement Downloads

Designelement Suche
Designelement Links

Designelement Impressum
Designelement Kontakt

Designelement Anmelden





Zu den C#.NET-Artikeln

  Verzeichnis rekursiv durchsuchen - C#
Am 16.10.2007 verfasst von Andreas Nägeli. Hits: 2896

Das Durchsuchen von Ordnerstrukturen wird immer wieder benötigt. Dieser Artikel soll Ihnen eine Methode zum rekursiven Suchen nach einer Datei in einem angegebenen Verzeichnis (plus Unterverzeichnisse) erklären. Dabei soll die gesamte Suche in eine extra Klasse ausgelagert werden, für die die folgenden Referenzen benötigt werden:

using System.Windows.Forms;
using System.IO;
using System.Threading;


Beginnen wir zunächst mit ein paar Delegates, die wir im späteren Verlauf benötigen werden. Ein Delegate ist ein Zeiger auf eine Funktion mit einer bestimmten Signatur. Wir werden diese Zeiger später benötigen, um Nachrichten an das Objekt zu senden, dass unsere Suchfunktion aufgerufen hat.

Die Definitionen von Delegates finden außerhalb von Klassen statt:

public delegate void onFileFound(String FilePath);
public delegate void onFinished();
public delegate void onAccessError(String Message);
public delegate void onCountChanged(int Folders, int Files);
public delegate void onFolderChanged(String Path);


Wie aus den Namen ersichtlich wird, werden wir diese Funktionen verwenden, wenn eine passende Datei gefunden oder der Suchvorgang abgeschlossen wurde, wenn ein Zugriffsfehler auftritt oder wenn sich die Anzahl an durchsuchten Ordnern geändert hat. Außerdem wird die letzte Methode dazu benutzt werden, den aktuell bearbeiteten Ordner anzuzeigen.

Selbstverständlich könnte man auch alle Statusobjekte an das Suchobjekt übergeben. Allerdings hätte dies den Nachteil, dass in der Suchlasse selbst bestimmt werden müsste, was in die Steuerelemente eingetragen wird. Auf die hier angewandte Weise bleibt mehr Spielraum für die aufrufende Klasse, die das Suchobjekt verwenden möchte.

Kommen wir nun zur eigentlichen Klasse CLRecursiveSearch:

public class CLRecursiveSearch {
  public onFileFound delegateFileFound;
  public onFinished delegateFinished;
  public onAccessError delegateAccessError;
  public onCountChanged delegateCountChanged;
  public onFolderChanged delegateFolderChanged;
  private Thread thread;
  private String current = "";
  private String searchfor = "";
  private int folders = -1, files = -1;
  private int level;

  public int getFolders() { return folders; }
  public int getFiles() { return files; }


Zunächst definieren wir 5 öffentliche Delegaten, die später verwendet werden können, um Methoden für Rückmeldungen aufzurufen. Die Variable thread wird später den Thread beinhalten, in dem wir die rekursive Suche an sich ausführen werden. current enthält das Verzeichnis, das gerade durchsucht wird, searchfor den Namen der Datei, die wir suchen. Die Integervariablen folders und files merken sich, wie viele Ordner bzw. Dateien wir bereits gefunden haben. Der Wert -1 deutet an, dass noch keine Suche stattgefunden hat. Zuletzt die Variable level. Sie merkt sich die Rekursionsebene auf der wir uns befinden. Dies wird später nötig sein um herauszufinden, wann wir am Ende der Suche angelangt sind. Die öffentlichen getMethoden können von der ausführenden Klasse verwendet werden, um die entsprechenden Variablen auszulesen.

Um später nach einer Datei zu suchen, werden wir die Methode Search aufrufen. Diese ist wie folgt definiert:

public void Search(String Filename, String Path) {
  if (Filename != "") {
    if (Directory.Exists(Path)) {
      folders = 1;
      files = 0;
      level = 0;
      searchfor = Filename;
      current = Path;
      thread = new Thread(new ThreadStart(Run));
      thread.Start();
    }
    else {
      throw new Exception("Path doesn't exist.");
    }
  }
  else {
    throw new Exception("Filename expected.");
  }
}


Zunächst prüfen wird, ob der zu suchende Dateiname nicht leer ist und ob das Suchverzeichnis existiert. Wenn hier ein Fehler auftritt, werfen wir eine entsprechende Exception. Danach setzen wir die Parameter für die Suche. folders wird hier auf 1 gesetzt, da das Ausgangsverzeichnis ebenfalls mitgezählt werden muss. Mit einem neuen Thread, der die Run-Methode ausführt, starten wir die Suche.

private void Run() {
  level++;
  if (delegateFolderChanged != null)
    delegateFolderChanged.Invoke(current);
  try {
    DirectoryInfo d = new DirectoryInfo(current);
    folders += d.GetDirectories().Length;
    files += d.GetFiles().Length;
    if (delegateCountChanged != null)
      delegateCountChanged.Invoke(folders, files);
    foreach (FileInfo f in d.GetFiles()) {
      if (delegateFileFound != null)
        if (f.Name == searchfor)
          delegateFileFound.Invoke(f.FullName);
    }
    foreach (DirectoryInfo tmp in d.GetDirectories()) {
      current = tmp.FullName;
      Run();
    }
  }
  catch {
    if (delegateAccessError != null)
      delegateAccessError.Invoke(current);
  }
  level--;
  if (level == 0) {
    if (delegateFinished != null)
      delegateFinished.Invoke();
  }
}


Mit jedem Durchlauf der Run-Methode gehen wir eine Stufe tiefer in die Ordnerstruktur. Deshalb inkrementieren wir hier zunächst den Zähler für die Rekursionsebene. Da sich der Ordner mit dem Aufruf der Funktion geändert hat, lösen wir die Funktion aus, die in delegateFolderChanged hinterlegt wurde.

Danach ermitteln wir mit der DirectoryInfo-Klasse alle Dateien und Unterverzeichnisse in diesem Ordner. Mit diesen Daten erhöhen wir die Anzahl an Dateien und gefundenen Verzeichnissen entsprechend. Nun gehen wir alle Dateien einzeln durch und prüfen, ob sich eine mit dem gesuchten Dateinamen in diesem Verzeichnis befindet. Wenn ja, lösen wir die entsprechende Funktion hinter delegateFileFound aus.

Nun setzen wir für jeden Unterordner in diesem Verzeichnis zunächst die current Variable und rufen dann erneut die Run-Methode auf. Dies ist der eigentliche Rekursionsschritt. Unsere Methode wird nun auch jeden einzelnen Unterordner durchsuchen, und sich selbst wiederum mit dessen Unterordnern aufrufen und so weiter - bis es irgendwann keine Unterordner mehr gibt und die Rekursionsebene wieder veringert wird.

Haben wir nicht die Rechte, um auf einen bestimmten Ordner zuzugreifen, so wird der catch-Block ausgeführt, in dem wiederum eine Delegate-Funktion ausgeführt wird. Abschließend müssen wir nur noch die Variable für die Rekursionsebene dekrementieren.

Haben wir am Ende dieser Funktion wieder die 0te Rekursionsstufe erreicht, so wissen wir, dass die Rekursion zu Ende ist. Dementsprechend können wir hier die passende Funktion aufrufen, die im Delegate hinterlegt wurde.

Da rekursive Algorithmen nicht einfach zu verstehen sind, möchte ich den Vorgang nochmals an einem Beispiel demonstrieren:

Beispielhafte Ordnerstruktur

Angenommen, wir füttern die Suche mit "C:" als Startverzeichnis für die rekursive Suche. Der Algorithmus listet dann zunächst alle Dateien in "C:" auf und wird nun für jedes Unterverzeichnis nochmals Run aufrufen.

Als erster Unterordner ist "Programme" an der Reihe. Sobald die Run-Methode mit current=C:\Programme aufgerufen wurde, wird der Ordner durchsucht und wieder wird mit der Abarbeitung der Unterverzeichnisse begonnen. Sind wir bei "C:\Programme\Programm I\Ordner I" angelangt ist es nicht mehr möglich, eine Stufe tiefer zu gehen. Das heißt, die foreach-Schleife mit den Unterordnern hat 0 Durchläufe und die Funktion beendet sich zum ersten mal regulär und springt eine Stufe nach oben. Als nächstes wird dann der zweite Ordner in der Liste, "Ordner II" durchsucht. Da auch hier nichts zu finden ist springen wir wieder eine Stufe höher, bemerken, dass wir in "Programm I" bereits alles erledigt haben und springen noch eine Stufe höher. Auch in "Programme" gibt es nichts mehr zu tun, so dass nun die Liste mit dem Windowsordner weitergeführt wird.

Die Parameter der Suche würden sich wie folgt auflisten lassen:

Rekursionsebenen

Was bleibt, ist nur noch die ausführende Klasse richtig zu implementieren. Dazu müssen wir nur die Delegates belegen und die Search-Methode ausführen:

private void button1_Click(object sender, EventArgs e) {
  lstFiles.Items.Clear();
  CLRecursiveSearch suche = new CLRecursiveSearch();
  suche.delegateFileFound += new onFileFound(FileFound);
  suche.delegateFinished += new onFinished(Finished);
  suche.delegateAccessError += new onAccessError(Error);
  suche.delegateCountChanged += new onCountChanged(Count);
  suche.delegateFolderChanged += new onFolderChanged(Changed);
  try {
    suche.Search(txtFile.Text, txtPath.Text);
  }
  catch (Exception exception) {
    MessageBox.Show(exception.Message, "Fehler beim Ausführen", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
  }
}


Die einzelnen Funktionen, die wir hier verwenden, müssen dann selbstverständlich noch angegegeben werden.

private void FileFound(String Path) {
  lstFiles.Items.Add(Path);
}

private void Finished() {
  lblPath.Text = "Suche abgeschlossen";
}

private void Error(String Message) {
  lstFiles.Items.Add("Zugriff verweigert: " + Message);
}

private void Count(int Folders, int Files) {
  lblStatus.Text = Files + " Dateien / " + Folders + " Verzeichnisse";
}

private void Changed(String Path) {
  lblPath.Text = Path;
}


Dieser Teil erklärt sich allerdings am besten aus dem Beispielprojekt selbst.

Den Algorithmus, den wir hier analysiert haben, können Sie auch für andere Zwecke verwenden, z.B. zum Indizieren von Verzeichnissen oder Ähnlichem.

Kommentiertes Codebeispiel herunterladen (CLRecursiveSearch.rar, 38 KB, VC80)

Bewertung dieses Artikels von 18 Benutzern: Mit 9 von 10 Punkten bewertet - 9.28 / 10 Punkte

Wie finden Sie diesen Artikel?











  2002 - 2008 Designelement Computerleben.net Designelement Sitemap