Anzahl der gelöschten Dateien in einem IEnumerable nach dem Löschvorgang verschwunden!?

Gestern bin ich über ein interessantes Phänomen gestolpert, das ich mir nicht recht erklären konnte. Die Aufgabe war einfach:

  • Aus einem Verzeichnis alle Dateien löschen, die einem bestimmten Filter entsprechen
  • Nach dem Löschvorgang die Anzahl der tatsächlich gelöschten Dateien ausgeben

 

Also erst alle zu löschenden Dateien in einem IEnumerable<string> sammeln und anschließend über File.Delete(…) in einer foreach-Schleife löschen.

Das Problem

Das Problem war nun, dass nach dem Durchlaufen der foreach-Schleife, das IEnumerable keine Einträge mehr enthielt. Es war einfach leer! Somit war die Ausgabe der Anzahl nicht mehr möglich.

Der Workaround dazu ist natürlich, die Anzahl vorher zu merken und anschließend auszugeben.

Das Problem hat mich aber weiter beschäftigt. Es wollte sich mir nicht erschließen, warum das IEnumerable leer sein sollte. Es enthält doch nur die Liste von Dateinamen!

Die Tests

Also habe ich mich dem Problem mit einer Reihe von Unit-Tests genähert.

Der erste Test durchläuft eine IEnumerable und gibt die Werte auf der Console aus:

Dieser Test ist – wenig überraschend – erfolgreich.

Im zweiten Test habe ich nun das Löschen mit aufgenommen.

Auch dieser Test läuft ohne einen Fehler durch.

Als Ursache Ausschließen können wir also die foreach-Schleife an sich und auch der Aufruf von File.Delete(…) führt nicht zu einem Fehler.

Die nächste Besonderheit im ursprünglichen Code war, dass wir für die Zusammenstellung der Dateien Linq verwendet haben.

Der nächste Test verwendet also Linq um das IEnumerable zu erzeugen.

Aber auch dieser Test bleibt grün, alles ist in Ordnung, das IEnumerable bleibt unverändert.

Ein weiterer Blick in den ursprünglichen Code zeigt nun, dass zum Füllen der IEnumerable kein einfaches string[]-Array verwendet wird, sondern eine Hilfsfunktion von Directory in Verbindung mit einem Linq-Ausdruck.

Diesen Code wollte ich also auch ausprobieren. Dazu musst ich aber nun tatsächlich das Verzeichnis „FilesToDelete“ mit den entsprechenden Dateien anlegen und im Visual Studio als „Copy if newer“ markieren.

Der Code des Tests sieht nun wie folgt aus:

Und siehe da…der Test schlägt tatsächlich fehl! Die Ursache liegt also irgendwo in diesem Code versteckt.

Ein weiterer Test sollte nun (das hier ohnehin sehr überflüssige) Linq als Ursache ausschließen.

Auch dieser Test schlägt fehl! Ha, es liegt also am Aufruf von Directory.EnumerateFiles(…).

 

Die Ursache

Aber warum wird die Liste im IEnumerable bei der Verwendung der Methode Directory.EnumerateFiles(…) nach dem Löschen der Datei geleert?

Ein Blick in den Source-Code der Directory-Klasse des .Net Frameworks zeigt die Ursache auf. Über einen recht langen Stacktrace wird irgendwann ganz unten in einer Factory folgender Code aufgerufen:

Es wird also kein IEnumerable über eine Liste von Strings erstellt, mit dem wir arbeiten, sondern es wird ein FileSystemEnumerableIterator<String> erstellt. Dieser wiederum ist so implementiert, dass er intern immer über die Methode Directory.GetDemandDir(…) auf das Dateisystem zugreift.

Nach dem Löschen sind die Dateien um Dateisystem natürlich nicht mehr vorhanden, weshalb die Liste in unserem obigen Test dann leer ist.

 

Zusammenfassung

Sicher lässt sich dieses Verhalten für andere Zwecke gut nutzen. Für den o.g. Zweck, allerdings nicht.

Nun, dann bleibt mir noch, Microsoft dafür zu danken, dass sie den Source-Code des .Net-Frameworks zur Verfügung gestellt haben, ohne den ich darauf sicher niemals gekommen wäre und mir für das Herausfinden selbst auf die Schulter zu klopfen.

Jetzt trinke ich mir erstmal den verdienten Kaffee und versuche beim nächsten Mal im Debugger früher auf den Typ der Interfaceimplementierung zu achten. Die war nämlich schon die ganze Zeit sichtbar…ich hätte nur hinschauen müssen:

Blog-FileSystemEnumerableIterator

 

 

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.