2010-09-20 11 views
57

Tengo el siguiente código:Cómo filtrar Directory.EnumerateFiles con múltiples criterios?

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);     
     } 

Trabaja muy bien y hace lo que necesito. Excepto por una pequeña cosa. Me gustaría encontrar una mejor manera de filtrar en múltiples extensiones. Me gustaría utilizar una matriz de cadenas con filtros como esto:

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

¿Cuál es la forma más eficiente de hacer esto utilizando .NET Framework 4.0/LINQ? ¿Alguna sugerencia?

lo agradecería cualquier ayuda ser un programador de vez en cuando :-)

+0

usted debe considerar el funcionamiento de cada búsqueda de extensión en paralelo. Creé algunos métodos útiles de ayuda en mi respuesta. Uno que toma una expresión regular, y uno que toma una lista de cuerdas. –

+0

Esta es una * muy * vieja pregunta (ya respondida adecuadamente por @MikaelSvenson), pero otra opción es usar la extensión Enumerable .Union(), así: foreach (archivo var en Directory.EnumerateFiles (ruta, "* .mp3 ", SearchOption.AllDirectories) .Union (Directory.EnumerateFiles (ruta," * .wma ", SearchOption.AllDirectories)) {...} – Kirkaiya

Respuesta

65

he creado algunos métodos de ayuda para resolver esto lo que me blogged acerca a principios de este año.

Una versión toma un patrón regex \.mp3|\.mp4, y la otra una lista de cadenas y se ejecuta en paralelo.

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)); 
    } 
} 
+0

Gracias por una buena implementación. ¿Cuál puede ser una buena forma (eficiente) de mostrar finalmente los resultados en la pantalla de WPF? Planeo usar tu método paralelo para obtener archivos. ¿Qué pasa si uso foreach para iterar los resultados y almacenarlos en una lista, y ellos los cargan en la pantalla? –

+0

Puede vincular al resultado de cualquiera de los métodos ya que el enlace enumerará todos los resultados por usted. No es necesario almacenarlo en una lista separada primero. La forma más eficiente es comenzar a mostrar elementos a medida que se enumeran. No soy un experto en WPF, pero supongo que deberías poder representar por artículo con alguna señalización. –

+0

¡Excelentes ejemplos! Solo para observar algunas características de cada uno de los dos métodos ... Con el método 'PARALLEL', las búsquedas NO son sensibles a mayúsculas y minúsculas, y los resultados que obtendrás estarán fuera de servicio. Con el método 'REGEX', las BÚSQUES SON sensibles a mayúsculas y minúsculas (a menos que uses algo como' "(? I) \. Mp3 $ | \ .mp4 $" '), y los resultados que obtendrás estarán en orden, como cabría esperar . He realizado pruebas y he notado que la versión paralela puede ejecutarse un poco LEVEMENTE más rápido, pero en general es una diferencia MUY pequeña. –

24

extrae del contexto LINQ, esto se reduce a la forma de saber si un archivo coincide con una lista de extensiones. System.IO.Path.GetExtension() es una mejor opción aquí que String.EndsWith(). El || múltiple se puede reemplazar por .Contains() o .IndexOf() dependiendo de la colección.

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

... s => extensions.Contains(Path.GetExtension(s)) 
+2

Debe eliminar los' * 's si desea hacer comparaciones de cadenas con ellos . – Gabe

+0

Y otra sugerencia sería usar una sobrecarga que permita verificaciones que no distingan entre mayúsculas y minúsculas. –

+1

Probablemente sea mejor usar un Hashset y una comparación insensible a mayúsculas y minúsculas. –

16
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); 
} 
+0

También necesita ".mp3", no "mp3". –

+0

Gracias funciona perfecto ... ** en mi caso ** Necesito agregar .ToArray() antes. Donde ... sin esto LINQ Query no funciona. – Equiman

3

Sé que esta es una publicación anterior, pero se me ocurrió una solución que a la gente le gustaría usar.

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)); 
} 
6

Como lo señaló en un comentario, mientras que los métodos de ayuda de Mikael Svenson son grandes pequeños soluciones, si alguna vez tratando de hacer algo para un proyecto de una sola vez a toda prisa de nuevo, tenga en cuenta la extensión de LINQ . Union(). Esto le permite unir dos secuencias enumerables. En su caso, el código se vería así:

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

Esto crea y llena su lista de resultados todo en una línea.

+1

Elegante, y evita la enumeración de todos los archivos por C#, lo que permite que el sistema de archivos optimice como sea posible. –

8

El enfoque más elegante es probablemente:

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

pero podría no ser la más eficiente.

0

de filtrar utilizando las mismas cadenas lista de extensiones de archivo como GUI abrir cuadros de diálogo ej .:

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

empaquetada:

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