Wie filtere ich Directory.EnumerateFiles mit mehreren Kriterien?

79

Ich habe folgenden Code:

List<string> result = new List<string>();

foreach (string file in Directory.EnumerateFiles(path,"*.*",  
      SearchOption.AllDirectories)
      .Where(s => s.EndsWith(".mp3") || s.EndsWith(".wma")))
       {
          result.Add(file);                 
       }

Es funktioniert gut und macht was ich brauche. Bis auf eine kleine Sache. Ich möchte einen besseren Weg finden, um nach mehreren Erweiterungen zu filtern. Ich möchte ein String-Array mit Filtern wie diesem verwenden:

string[] extensions = { "*.mp3", "*.wma", "*.mp4", "*.wav" };

Was ist der effizienteste Weg, dies mit NET Framework 4.0 / LINQ zu tun? Irgendwelche Vorschläge?

Ich würde mich über jede Hilfe als gelegentlicher Programmierer freuen :-)

Yooakim
quelle
Sie sollten in Betracht ziehen, jede Erweiterungssuche parallel auszuführen. In meiner Antwort habe ich einige nützliche Hilfsmethoden erstellt. Eine, die einen regulären Ausdruck verwendet, und eine, die eine Zeichenfolgenliste verwendet.
Mikael Svenson
2
Dies ist eine sehr alte Frage (bereits von @MikaelSvenson angemessen beantwortet), aber eine andere Option ist die Verwendung der Enumerable-Erweiterung .Union (), wie folgt: foreach (var-Datei in Directory.EnumerateFiles (Pfad, " .mp3", SearchOption). AllDirectories) .Union (Directory.EnumerateFiles (Pfad, " .wma", SearchOption.AllDirectories)) {...}
Kirkaiya

Antworten:

86

Ich habe einige Hilfsmethoden entwickelt, um dieses Problem zu lösen, über die ich Anfang dieses Jahres gebloggt habe .

Eine Version verwendet ein Regex-Muster \.mp3|\.mp4und die andere eine Zeichenfolgenliste und wird parallel ausgeführt.

public static class MyDirectory
{   // Regex version
   public static IEnumerable<string> GetFiles(string path, 
                       string searchPatternExpression = "",
                       SearchOption searchOption = SearchOption.TopDirectoryOnly)
   {
      Regex reSearchPattern = new Regex(searchPatternExpression, RegexOptions.IgnoreCase);
      return Directory.EnumerateFiles(path, "*", searchOption)
                      .Where(file =>
                               reSearchPattern.IsMatch(Path.GetExtension(file)));
   }

   // Takes same patterns, and executes in parallel
   public static IEnumerable<string> GetFiles(string path, 
                       string[] searchPatterns, 
                       SearchOption searchOption = SearchOption.TopDirectoryOnly)
   {
      return searchPatterns.AsParallel()
             .SelectMany(searchPattern => 
                    Directory.EnumerateFiles(path, searchPattern, searchOption));
   }
}
Mikael Svenson
quelle
Vielen Dank für eine gute Umsetzung. Was kann ein guter (effizienter) Weg sein, um die Ergebnisse endlich auf dem WPF-Bildschirm anzuzeigen? Ich plane, Ihre parallele Methode zu verwenden, um Dateien zu erhalten. Was ist, wenn ich foreach verwende, um die Ergebnisse zu iterieren und in einer Liste zu speichern, und sie auf dem Bildschirm laden?
Saurabh Kumar
Sie können einfach an die Ausgabe beider Methoden binden, da die Bindung alle Ergebnisse für Sie auflistet. Sie müssen es nicht zuerst in einer separaten Liste speichern. Am effizientesten ist es, Elemente so anzuzeigen, wie sie aufgelistet sind. Ich bin kein WPF-Experte, aber ich denke, Sie sollten in der Lage sein, pro Element mit einigen Signalen zu rendern.
Mikael Svenson
Tolle Beispiele! PARALLELUm nur einige Merkmale jeder der beiden Methoden zu nennen ... Bei der Methode wird bei der Suche NICHT zwischen Groß- und Kleinschreibung unterschieden, und die Ergebnisse, die Sie erhalten, sind nicht in Ordnung. Bei dieser REGEXMethode wird bei der Suche zwischen Groß- und Kleinschreibung unterschieden (es sei denn, Sie verwenden etwas Ähnliches "(?i)\.mp3$|\.mp4$"), und die Ergebnisse, die Sie erhalten, sind in der erwarteten Reihenfolge. Ich habe Tests durchgeführt und festgestellt, dass die parallele Version möglicherweise etwas leichter läuft, aber insgesamt ein SEHR kleiner Unterschied.
Arvo Bowen
@ArvoBowen guter Fang auf den Groß- und Kleinschreibung Vergleich, und fügte eine Regexoption in den Code
Mikael Svenson
Dies ist eine großartige Lösung. Vielen Dank! Nur zu Ihrer Information: Ich bin auf Leistungsprobleme gestoßen, die ich auf IEnumerable zurückgeführt habe (hauptsächlich um die Count () -Methode, die ich an einigen Stellen verwendet habe, aber das war nicht der einzige Leistungseinbruch). Meine Liste hatte ungefähr 4700 Dateinamen. Ich habe ein .ToArray () auf der Liste gemacht und alles als Array behandelt. Sie zahlen einen einmaligen Preis, der die Liste in ein Array verwandelt, aber dies wird durch die spürbar schnellere Leistung danach mehr als gemildert.
Toolsmythe
30

Aus dem LINQ-Kontext entfernt, müssen Sie herausfinden, ob eine Datei mit einer Liste von Erweiterungen übereinstimmt. System.IO.Path.GetExtension()ist hier eine bessere Wahl als String.EndsWith(). Das Vielfache ||kann durch .Contains()oder .IndexOf()abhängig von der Sammlung ersetzt werden.

var extensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)  
   {  ".mp3", ".wma", ".mp4", ".wav" };

...  s => extensions.Contains(Path.GetExtension(s))
Henk Holterman
quelle
2
Sie müssen das *s entfernen, wenn Sie Zeichenfolgenvergleiche mit ihnen durchführen möchten.
Gabe
Ein weiterer Vorschlag wäre die Verwendung einer Überlastung, die eine Prüfung ohne Berücksichtigung der Groß- und Kleinschreibung ermöglicht.
Dirk Vollmar
1
Wahrscheinlich ist es besser, ein Hashset und einen Vergleich ohne Berücksichtigung der Groß- und Kleinschreibung zu verwenden.
Jim Mischel
2
Sie müssen den Punkt (.mp3) einfügen. Verwenden Sie string.ToLower (), um den Fall zu behandeln.
Hans Passant
@Hans, richtig über den Punkt, aber wäre ToLower () besser als OrdinalIgnoreCase? Nur eine Auswahl: stackoverflow.com/questions/501906/…
Henk Holterman
19

Der eleganteste Ansatz ist wahrscheinlich:

var directory = new DirectoryInfo(path);
var masks = new[] { "*.mp3", "*.wav" };
var files = masks.SelectMany(directory.EnumerateFiles);

Aber es ist vielleicht nicht das effizienteste.

Tom Pažourek
quelle
Wie füge ich Unterordner hinzu?
Asen Kasimov
1
@AsenKasimov Die EnumerateFiles-Methode hat eine Überladung mit einem Parameter, den Sie angeben können, um auch in Unterordnern zu suchen.
Tom Pažourek
18
string path = "C:\\";
var result = new List<string>();
string[] extensions = { ".mp3", ".wma", ".mp4", ".wav" };

foreach (string file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
    .Where(s => extensions.Any(ext => ext == Path.GetExtension(s))))
{
    result.Add(file);
    Console.WriteLine(file);
}
Islam Yahiatene
quelle
Sie benötigen auch ".mp3", nicht "mp3".
Henk Holterman
Danke funktioniert perfekt ... in meinem Fall muss ich .ToArray () vor .Where hinzufügen ... ohne diese LINQ-Abfrage funktioniert das nicht.
Equiman
12

Wie ich in einem Kommentar bemerkte, sind die Hilfsmethoden von Mikael Svenson zwar großartige kleine Lösungen, aber wenn Sie jemals wieder versuchen, in Eile etwas für ein einmaliges Projekt zu tun, sollten Sie die Linq-Erweiterung .Union () in Betracht ziehen . Auf diese Weise können Sie zwei aufzählbare Sequenzen zusammenfügen. In Ihrem Fall würde der Code folgendermaßen aussehen:

List<string> result = Directory.EnumerateFiles(path,"*.mp3", SearchOption.AllDirectories)
.Union(Directory.EnumerateFiles(path, ".wma", SearchOption.AllDirectories)).ToList();

Dadurch wird Ihre Ergebnisliste in einer Zeile erstellt und gefüllt.

Kirkaiya
quelle
2
Elegant und vermeidet die Aufzählung aller Dateien durch C #, sodass das Dateisystem optimieren kann, wie es kann.
Craig Brunetti
2

Ich weiß, dass dies ein alter Beitrag ist, aber ich habe eine Lösung gefunden, die die Leute vielleicht gerne verwenden würden.

private IEnumerable<FileInfo> FindFiles()
{
    DirectoryInfo sourceDirectory = new DirectoryInfo(@"C:\temp\mydirectory");
    string foldersFilter = "*bin*,*obj*";
    string fileTypesFilter = "*.mp3,*.wma,*.mp4,*.wav";

    // filter by folder name and extension
    IEnumerable<DirectoryInfo> directories = foldersFilter.Split(',').SelectMany(pattern => sourceDirectory.EnumerateDirectories(pattern, SearchOption.AllDirectories));
    List<FileInfo> files = new List<FileInfo>();
    files.AddRange(directories.SelectMany(dir => fileTypesFilter.Split(',').SelectMany(pattern => dir.EnumerateFiles(pattern, SearchOption.AllDirectories))));

    // Pick up root files
    files.AddRange(fileTypesFilter.Split(',').SelectMany(pattern => sourceDirectory.EnumerateFiles(fileTypesFilter, SearchOption.TopDirectoryOnly)));

    // filter just by extension
    IEnumerable<FileInfo> files2 = fileTypesFilter.Split(',').SelectMany(pattern => sourceDirectory.EnumerateFiles(pattern, SearchOption.AllDirectories));
}
kmcbrearty
quelle
1

Zum Filtern mit denselben Dateierweiterungen Listenzeichenfolgen wie GUI Open Dialogs, z.

".exe,.pdb".Split(',', ';', '|').SelectMany(_ => Directory.EnumerateFiles(".", "*" + _, searchOptions)

Verpackt:

    public static IEnumerable<string> EnumerateFilesFilter(string path, string filesFilter, SearchOption searchOption = SearchOption.TopDirectoryOnly)
    {
        return filesFilter.Split(',', ';', '|').SelectMany(_ => Directory.EnumerateFiles(path, "*" + _, searchOption));
    }
Matthew Sheeran
quelle
0

Ich habe dieses Problem folgendermaßen gelöst:

string[] formats = {".mp3", ".wma", ".mp4"};

foreach (var file in Directory.EnumerateFiles(folder, "*.*", SearchOption.AllDirectories).Where(x => formats.Any(x.EndsWith)))
{
    // TODO...
}
Витёк Синёв
quelle