Durchsuchen von Ordnern mit Ordnertiefe

  • VB.NET

Es gibt 32 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Dann gehen wir mal vom fast-worst-case aus, dass der TE n ganzes Laufwerk durch seine Funktion jagen will. Ich bin's mal bei mir durchgegangen. Bei mir reichen 2 Ausnahmetypen, um C:\ durchzuackern. Type1 ist bei Du-hast-hier-nix-zu-suchen-Fällen, wie von MrMo angegeben wurde. Type2 wurde bei mir notwendig, als die Funktion auf eine Verknüpfung zu einem nicht-mehr-existenten Verzeichnis stieß:

    VB.NET-Quellcode

    1. Dim Verzeichnisnamen() As String = Nothing, Dateinamen() As String = Nothing
    2. Try
    3. Verzeichnisnamen = IO.Directory.GetDirectories(Pfad)
    4. Dateinamen = IO.Directory.GetFiles(Pfad)
    5. Catch tp_ExceptionType1 As UnauthorizedAccessException
    6. Return
    7. Catch tp_ExceptionType2 As IO.DirectoryNotFoundException
    8. Return
    9. End Try


    Die string-Arrays sollten später in den For Each-Schleifen wiederverwendet werden, da ein wiederholter GetDirectories-Aufruf später in der Funktion das komplette C:\-Durchgehen zeitlich bei mir ver9fachte. Von einem erneuten GetFiles-Aufruf ganz zu schweigen.

    @exc-jdbi: Hab's mal mit Attributen probiert. Bei nem Papierkorb-Ordner mit den Attributen IO.FileAttributes.System, IO.FileAttributes.Hidden und IO.FileAttributes.Directory bleibt der Debugger hängen, bei anderen Verzeichnissen mit gleichen Attributen gab's keine Probleme. Aber Verzeichnisattribute standen noch nicht in meinen Programmierplänen, vielleicht kann da noch jemand mit often-used-code weiterhelfen.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Zugriffsrechte

    Keine Ahnung ob das hilft, kann das mal jemand ausprobieren, der solche Verzeichnisse besitzt auf denen keine Zugriffsrechte vorhanden sind?

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Option Strict On
    2. Option Explicit On
    3. Imports System.IO
    4. Imports System.Security
    5. Imports System.Security.AccessControl
    6. Public Module Module1
    7. Private Enum FileSystemObject
    8. InvalidPath
    9. Directory
    10. File
    11. End Enum
    12. Public Sub Main()
    13. Dim sPath As String = "C:\"
    14. For Each d As String In Directory.GetDirectories(sPath)
    15. If CheckFolderFilePermissions(d) Then
    16. Console.WriteLine("{1}{0}{2}", vbTab, d, True)
    17. Else : Console.WriteLine("{1}{0}{2}", vbTab, d, False)
    18. End If
    19. Next
    20. Console.ReadKey()
    21. End Sub
    22. Private Function CheckFolderFile(ByVal sPath As String) As FileSystemObject
    23. If Directory.Exists(sPath) Then
    24. Return FileSystemObject.Directory
    25. ElseIf File.Exists(sPath) Then
    26. Return FileSystemObject.File
    27. End If
    28. Return FileSystemObject.InvalidPath
    29. End Function
    30. Private Function CheckFolderFilePermissions(ByVal sPath As String) As Boolean
    31. If sPath.Length > 0 Then
    32. Dim fso As FileSystemObject = CheckFolderFile(sPath)
    33. If Not fso = FileSystemObject.InvalidPath Then
    34. Return CheckFolderFilePermissions(sPath, fso, FileSystemRights.ReadPermissions)
    35. End If
    36. End If
    37. Return False
    38. End Function
    39. Private Function CheckFolderFilePermissions(ByVal sPath As String, ByVal fso As FileSystemObject, ByVal accessType As FileSystemRights) As Boolean
    40. Dim collection As AuthorizationRuleCollection = Nothing
    41. Try
    42. If fso = FileSystemObject.Directory Then
    43. collection = Directory.GetAccessControl(sPath).GetAccessRules(True, True, GetType(System.Security.Principal.NTAccount))
    44. ElseIf fso = FileSystemObject.File Then
    45. collection = File.GetAccessControl(sPath).GetAccessRules(True, True, GetType(System.Security.Principal.NTAccount))
    46. End If
    47. Catch ex As UnauthorizedAccessException
    48. Return False
    49. End Try
    50. For Each rule As FileSystemAccessRule In collection
    51. If (rule.FileSystemRights And accessType) > 0 Then
    52. Return True
    53. End If
    54. Next
    55. Return True
    56. End Function
    57. End Module



    EDIT: Code komplett überarbeitet

    Freundliche Grüsse

    exc-jdbi

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „exc-jdbi“ ()

    Hab neulich erst ein wenig mit Verzeichnissen und Rechten rum gespielt.

    Lass mal Directory.GetDirectories() komplett über C:/ (AllDirectories) laufen und versuch dabei deine Rechte zu ermitteln. Da hast du keine Chance. Die Exception fliegt, sobald .GetDirectories() aufgerufen wird.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Also... ich hab das gestern auch mal probiert und es auf folgendes runtergebrochen...

    VB.NET-Quellcode

    1. Private Function GetFolders(ByVal Folder As String, ByVal MaxDepth As Integer, Optional ByVal Found As List(Of IO.DirectoryInfo) = Nothing, Optional ByVal Depth As Integer = 0) As List(Of IO.DirectoryInfo)
    2. Dim _Found As New List(Of IO.DirectoryInfo)
    3. Depth += 1
    4. If Found Is Nothing Then
    5. Found = New List(Of IO.DirectoryInfo)
    6. Found.Add(New IO.DirectoryInfo(Folder))
    7. End If
    8. For Each _Folder In Found
    9. Try : _Found.AddRange(_Folder.GetDirectories("*", IO.SearchOption.TopDirectoryOnly)) : Catch ex As Exception : End Try
    10. Next
    11. If Depth >= MaxDepth OrElse _Found Is Nothing Then
    12. Return Found
    13. Else
    14. For Each f As IO.DirectoryInfo In GetFolders(Folder, MaxDepth, _Found, Depth)
    15. Found.Add(f)
    16. Next
    17. Return Found
    18. End If
    19. End Function

    Kleines Beispiel anbei.
    Dateien
    • TestPfadtiefe.zip

      (103,16 kB, 104 mal heruntergeladen, zuletzt: )
    Es war einmal ein kleiner Bär... der wollte eine Geschichte hörn... Da erzählte ihm seine Mutti:
    Es war einmal ein kleiner Bär... der wollte eine Geschichte hörn... Da erzählte ihm seine Mutti:
    Es war einmal ein kleiner Bär... der wollte eine Geschichte hörn... Da erzählte ihm seine Mutti:
    ... Nun solltest es selber wissen. :'D

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „MemoAnMichSelbst“ ()

    Hi
    ein Verbesserungsvorschlag: Verwende die Linq-Extension SelectMany. SelectMany bildet eine Auflistung aus vielen Auflistungen. Das kann man dann wunderbar auf die Rekursion anwenden.
    Verwendet außerdem nicht die List(Of T) sondern direkt Enumerable. Das mit der Liste kann der Aufrufer dann erledigen, sofern es gewünscht ist. Wenn man das nicht macht, erzeugt man redundanten Code, sofern eine ähnliche Aufgabe erneut auf einen zukommt, bzw. muss die Arbeit später erledigen. Firmen würde ich sowieso empfehlen, solch generische Dinge in einer "Utility-Bibliothek" anzulegen, wie auch immer die aussieht und die Sachen entsprechend zu taggen.

    Die Neuerung mit

    VB.NET-Quellcode

    1. Dim userName As String = "~blaze~"
    2. Dim str As String = $"Hello, my name is {userName}."

    ziehe ich übrigens dem String.Format sehr vor, zumal das auch (ohne Optimierungsalgorithmen) schon zur Compilezeit aufgelöst werden kann. Im obigen Snippet wird halt {userName} mit dem Inhalt der Variablen userName ersetzt.

    Edit: Hier mal meine Variante:

    VB.NET-Quellcode

    1. Shared Function EnumerateDirectories(root As DirectoryInfo, maxDepth As Integer) As IEnumerable(Of DirectoryInfo)
    2. If maxDepth = 0 Then Return Nothing
    3. 'Wird benötigt, da SelectMany leider Null-Referenzen (Nothing) nicht überspringt
    4. Static emptyDirectoryArray As DirectoryInfo() = New DirectoryInfo() {}
    5. Dim directories As IEnumerable(Of DirectoryInfo)
    6. Try
    7. 'Unterverzeichnise auflisten
    8. directories = root.EnumerateDirectories()
    9. Catch ex As Exception When _
    10. TypeOf ex Is UnauthorizedAccessException OrElse
    11. TypeOf ex Is DirectoryNotFoundException
    12. 'Abbruch, sofern ein Fehler auftritt, in diesem Fall hab' ich mich mal für When entschieden, da ich sonst zwei mal Return Nothing durchführen müsste, bzw. Änderungen doppelt vorhanden wären
    13. Return Nothing
    14. End Try
    15. 'Sofern die maximale Tiefe noch nicht erreicht wurde, wird die nächste Ebene ebenfalls in die Auflistung übernommen
    16. 'directories enthält entsprechend sich selbst und alle Unterverzeichnisse, auf die ebenfalls EnumerateDirectories angewandt wird
    17. If maxDepth > 0 Then directories = directories.Concat(directories.SelectMany(Function(dir) If(EnumerateDirectories(dir, maxDepth - 1), emptyDirectoryArray)))
    18. Return directories
    19. End Function

    Testcode:

    VB.NET-Quellcode

    1. Dim maximumCount As Integer = 300 'Maximal maximumCount Ergebnisse ausgeben
    2. Dim root As String = "C:\" 'Stammverzeichnis, auf dem operiert wird
    3. Dim maxDepth As Integer = 4 'Maximale Tiefe der Elemente, die ausgegeben werden
    4. 'Quick & dirty noch schnell die Eingabe etwas anpassen (nur für den Test, sowas würde ich niemals in einer Bibliothek machen)
    5. Dim dirsep As Char = Path.DirectorySeparatorChar
    6. root = root.Replace(Path.AltDirectorySeparatorChar, dirsep) '/ durch \ ersetzen
    7. If Not root.EndsWith(dirsep) Then root &= dirsep
    8. 'Sich auf lazy evaluation verlassen, die Auswertung dauert vergleichsweise kurz
    9. For Each v In EnumerateDirectories(New DirectoryInfo(root), maxDepth)
    10. If maximumCount = 0 Then Exit For
    11. 'Ausgabe der Tiefe des momentan angesteuerten Elements über die Zahl der directory separator-Zeichen ('\') relativ zum Stammverzeichnis und Verzeichnisnamen ausgeben
    12. Debug.WriteLine($"Depth: {v.FullName.Count(Function(c) c = dirsep) - root.Count(Function(c) c = dirsep) + 1}: {v.FullName}")
    13. maximumCount -= 1
    14. Next


    Viele Grüße
    ~blaze~

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „~blaze~“ ()

    Danke für eure vielen Vorschläge, dabei kann man gut lernen, wie es richtig geht!

    Also ich habe einfach mal weiter gemacht und eure Vorschläge berücksichtigt.

    @MemoAnMichSelbst Das DataGridView Element finde ich zur Darstellung sehr gut!

    @exc-jdbi Das Exception Handling finde ich sehr gelungen!

    @Alle: man merkt schon, dass Ihr das sehr regelmäßig, vermutlich auch professionell macht! Manche Ausführungen hier lassen das schon schnell erkennen.

    Mich würde einfach mal interessieren, welche weiteren Verbesserungen ihr gerade habt. Beim Exception Handling habe ich mich an das Beispiel von hier gehalten. Ich weiß aber nicht ob das professionell auch so gemacht wird?
    Außerdem würde mich interessieren, was noch besser gemacht werden könnte. Ich habe aktuell auch noch Bedenken bei My.Computer.... ich glaube, dass das nicht mehr gängige Praxis ist?!



    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Function TestFunktion_Beta(Pfad As String, aktuelleTiefe As Integer, maximaleTiefe As Integer)
    2. If aktuelleTiefe = maximaleTiefe Then
    3. Return 0
    4. End If
    5. Try
    6. For Each strFile As String In Directory.GetFiles(Pfad)
    7. Next
    8. For Each Verzeichnis As String In Directory.GetDirectories(Pfad)
    9. TestFunktion_Beta(Verzeichnis, aktuelleTiefe + 1, maximaleTiefe)
    10. Dim dt As DateTime = Directory.GetLastWriteTime(Verzeichnis)
    11. Dim dt2 As DateTime = Directory.GetCreationTime(Verzeichnis)
    12. Dim InfoPfad As System.IO.DirectoryInfo
    13. InfoPfad = My.Computer.FileSystem.GetDirectoryInfo(Verzeichnis)
    14. Dim alleAttribute As System.IO.FileAttributes
    15. alleAttribute = InfoPfad.Attributes
    16. DataGridView1.Rows.Add(Verzeichnis, dt, dt2, alleAttribute)
    17. Next
    18. Catch ex As Exception
    19. MessageBox.Show("Fehler: " & ex.ToString)
    20. Catch ex As UnauthorizedAccessException
    21. MessageBox.Show("Fehler: " & ex.ToString)
    22. End Try
    23. End Function


    VB.neter0101 schrieb:

    VB.NET-Quellcode

    1. Catch ex As Exception
    solltest Du einfach weglassen, denn die ist die Basisklasse aller Exceptions und fängt dann den Rest weg, der eigentlich durch gute Programmierung nicht auftreten sollte.
    Wichtig ist noch, dass Du an die Fehlerzeile rankommst, also müsstest Du den StackTrace in der Exception-Instanz auslesen, dies entfällt jedoch, wenn die Exception da knallt, wo sie geworfen wird.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Prinzipiell sollte man auf diese Try Catch und co gänzlich verzichten.
    Leider isses bei der Funktion .GetDirectories so ne sache, da man dort keine Möglichkeit hat gescheit drauf zu reagieren.
    Allgemein heißt ne Exception ja, dass da was passiert, was das Programm instabil macht. Also lieber die Fehlermeldung fliegen lassen und den Fehler direkt abfangen. Was sich MS Dabei gedacht hat, als sie die Funktion derart eingebaut haben, weiß ich leider auch net recht.
    Wenn du nur MessageBox.Show("Fehler: " & ex.ToString) wirfst, wirst du nachher ein Problem bekommen, wen nein User dir das dort gemeldete per Screenshot schickt. Und vor allem, wenn das ganze Folgefehler wirft (ist ja in den meisten Fällen so).
    Wenn du selber noch entwickelst wirfst dich damit sogar beim Debuggen ganz ausm Code raus... Also erst recht ungut.
    Es war einmal ein kleiner Bär... der wollte eine Geschichte hörn... Da erzählte ihm seine Mutti:
    Es war einmal ein kleiner Bär... der wollte eine Geschichte hörn... Da erzählte ihm seine Mutti:
    Es war einmal ein kleiner Bär... der wollte eine Geschichte hörn... Da erzählte ihm seine Mutti:
    ... Nun solltest es selber wissen. :'D
    Ich glaube, ich würde tatsächlich auf das mit dem When zugreifen. Das macht meines Erachtens Sinn, da es sonst zu Redundanz führt. Alternativ kann man natürlich auch Exception fangen, auf den Typ überprüfen und über Throw die Exceptions, die nicht gewünscht sind, wieder werfen. Aber das bringt keinen Vorteil.
    Es gilt einfach, dass man Redundanz vermeiden soll. Machen zwei Dinge das Gleiche (bzw. etwas Ähnliches), finde eine Lösung, die die beiden Dinge zu einer gemeinsamen Lösung zusammenfasst.

    Ich glaube, dass meine Lösung schon sehr nah an der "professionellen Lösung" ist, die Testmethode mal außenvor gelassen (wobei ich denke, dass professionelle Tests auch so aussehen können). Mir würde nur Errorhandling noch einfallen.
    Was mir an deinem Code nicht gefällt, ist, dass du keine Datenbindung für das DataGridView zu verwenden scheinst. Außerdem, wie du schon angemerkt hast, ist es wohl besser auf My.Computer, usw. zu verzichten, wobei ich mit dieser Ansicht nicht ganz im Reinen bin - da könnte man jetzt diskutieren. Außerdem definierst du eine Funktion, hast aber gleichzeitig keinen Rückgabewert (und auch keinen Rückgabetypen, ist Option Strict auf On? Falls nicht, unbedingt für dieses und für alle Projekte machen!). Verwende Sub statt Function, wenn eine Methode keinen Rückgabewert besitzt.

    Ansonsten noch ein paar Details, bei denen ich mir nicht sicher bin, ob das nur zur Demo so ist: Die Benennung der Steuerelemente solltest du unbedingt überarbeiten. DataGridView1 ist nicht aussagekräftig. Selbiges gilt natürlich auch für TestFunktion_Beta MessageBox solltest du so überladen, dass es ein Icon und eine Kopfzeile besitzt. Außerdem sollte der Zustand nach Try-Catch wiederhergestellt werden, d.h. alle Zeilen müsstest du löschen.

    @MemoAnMichSelbst
    Man sollte nicht gänzlich auf Try-Catch verzichten. Ich weiß nicht, wieso sich das hier so festgesetzt hat. Die korrekte Aussage sollte meiner Meinung nach eher sein "Man kann fast gänzlich darauf verzichten". Es gibt eine Reihe von Fällen, in denen Exceptions dazu benutzt werden, mit dem Benutzer zu kommunizieren. Das ist bei IDictionary.Add, Dateisicherheit, Netzwerkübertragung, uvm. der Fall, in denen beabsichtigterweise die Ausführung unterbrochen wird, da ggf. ein Zustand erreicht wird, in dem nicht garantiert ist, dass das, was herauskommt, das ist, was vom Benutzer so beabsichtigt ist. Die Fehler an dieser Stelle abzufragen dient dann dazu, dem Programm aufzuzeigen "Ich hab daran gedacht, ist schon richtig so, was ich mache".
    Die Programmierer haben sich dabei gedacht, dass die Abfrage auf Zugriffsrechte nicht doppelt durchgeführt werden muss. Wenn du erst abfragst, ob du berechtigt bist und dann Code aufrufst, der wiederum abfragt, ob du berechtigt bist, ist das Errorhandling unelegant. Ein unerwarteter Zustand wird ja in diesem Fall eh nicht erreicht.
    Das Gleiche ist übrigens auch bei File.Exists und CreateNew der Fall. Einfach abfragen, ob CreateNew eine entsprechende Exception wirft, die Kombination von Exists und Create garantiert ja noch nicht mal, dass die Datei nicht doch überschrieben wurde.
    Try-Catch ist unbrauchbar, wenn versucht wird, Fehler einfach zu verschlucken. Wenn nicht mehr dafür gesorgt wird, dass der ursprüngliche oder ein gültiger Zustand erreicht wird, ist Try-Catch sogar ein Risiko und genau darauf zielt das mit dem "Verzicht" ab. ;)

    Viele Grüße
    ~blaze~

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

    ~blaze~ schrieb:

    Man kann fast gänzlich darauf verzichten
    Imo auch noch bisserl unvollständig.
    Weil wenn man drauf verzichten kann, dann sollte man das auch tun (das drauf verzichten).
    Aber bei globalen Dateisuchen kann man eben nicht mit vertretbarem Aufwandt drauf verzichten. Daher muss man hier die UnauthorisizedAccessException fangen - andere Exceptions sind mir nicht bekannt, die ebenfalls zu tolerieren wären.
    Und natürlich ist nur eine einzige Zeile in den Try einzuschließen - nicht zig Zeilen oder sogar Schleifen (wie im obigen Snippet).

    ~blaze~ schrieb:

    Ich weiß nicht, wieso sich das hier so festgesetzt hat.
    Naja, hofflich hab ich ein Teil dazu beigetragen, weil ich vertrete das ja recht vehement.
    Aber in differenzierter Form: "TryCatch wenn möglich vermeiden, und das ist sehr oft möglich, aber eben nicht immer".

    ErfinderDesRades schrieb:

    Weil wenn man drauf verzichten kann, dann sollte man das auch tun (das drauf verzichten).


    Nein, stimmt nicht, bzw. sag zumindest ich.
    Dictionary.Add bspw. wirft eine Exception, wenn ein Schlüssel doppelt vergeben wird. TryGetValue, bzw. Contains erzeugen eine redundante Abfrage, die auf IDictionary.Add eine unbekannte Komplexität hat und die Zeit frisst und unnötig ist. Ergo: Try-Catch. Was mich eher stört, ist, dass es keine Add(TKey, Func<TValue>)-Überladung gibt.
    Aber warum sollte man hier überhaupt auf Try-Catch verzichten? Der einzige Grund, der mir hier einfiele ist, dass das Catch teuer ist - tritt aber in den meisten Fällen eh nicht auf und das Try selbst ist nicht teuer. Try-Catch ist ein Instrument, das es zu benutzen gilt, wenn es sinnvoll ist und in Fällen, in denen die Benutzung vorgesehen ist, gilt es eben auch, es zu benutzen.

    Viele Grüße
    ~blaze~
    Dictionary(Of Tkey, TValue).TryGetValue() erzeugt AFAIK keine redundante Abfrage - zumindest in dem Fall ist TryCatch ganz unangebracht.
    Dumm halt, dass IDictionary diesen sehr nützlichen Member nicht vorsieht.
    Und ein TryAdd gibts ühaupt nicht, da muss man evtl. Tryen, wenn einem das Zuweisungs-Verhalten nicht zusagt.

    Jdfs. diese Try-Methoden (auch zB Integer.TryParse etc.) zeigen genau eine Architektur auf, darauf designed, TryCatch zu erübrigen.