如何使用多个条件过滤Directory.EnumerateFiles?


78

我有以下代码:

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

它工作正常,可以满足我的需求。除了一件小事。我想找到一种更好的方法来过滤多个扩展。我想使用带有这样的过滤器的字符串数组:

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

使用NET Framework 4.0 / LINQ进行此操作的最有效方法是什么?有什么建议么?

我非常感谢偶尔的程序员提供的帮助:-)


您应该考虑并行运行每个扩展搜索。我在答案中创建了一些有用的帮助程序方法。一个使用正则表达式,另一个使用字符串列表。
Mikael Svenson

2
这是一个非常老的问题(已经由@MikaelSvenson适当地回答了),但是另一个选择是使用Enumerable扩展名.Union(),如下所示:foreach(Directory.EnumerateFiles(path,“ .mp3”,SearchOption中的var文件)。 AllDirectories).Union(Directory.EnumerateFiles(path,“ .wma”,SearchOption.AllDirectories)){...}
Kirkaiya

Answers:


86

我创建了一些辅助方法来解决这个问题,这是我今年早些时候写的博客

一个版本使用regex模式\.mp3|\.mp4,另一个版本使用字符串列表并并行运行。

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

感谢您的良好实施。什么是在WPF屏幕上最终显示结果的好(有效)方法?我计划使用您的并行方法来获取文件。如果我使用foreach迭代结果并将它们存储在列表中,然后将它们加载到屏幕上怎么办?
Saurabh Kumar 2012年

您可以只绑定到这两种方法的输出,因为绑定将为您枚举所有结果。无需先将其存储在单独的列表中。最有效的方法是开始显示枚举的项目。我不是WPF专家,但我想您应该能够通过一些信号渲染每个项目。
Mikael Svenson 2012年

很好的例子!只是要注意这两种方法中每种方法的一些特征...使用该PARALLEL方法,搜索不区分大小写,并且您将获得的结果将是混乱的。使用该REGEX方法,“搜索”区分大小写(除非您使用"(?i)\.mp3$|\.mp4$"),否则您将获得的结果将与您期望的一致。我进行了测试,发现并行版本运行SLIGHT的速度可能稍快,但差异很小。
Arvo Bowen

@ArvoBowen很好地区分了大小写比较,并在代码中添加了regexoption
Mikael Svenson

这是一个很好的解决方案。谢谢!仅供参考:我遇到了性能问题,可追溯到IEnumerable(主要集中在我在几个地方使用的Count()方法,但这并不是唯一的性能问题)。我的名单上有大约4700个文件名。我结束了在列表上执行.ToArray()并将所有内容作为数组处理的问题。您只需支付一次费用,即可将列表转换为数组,但此后的显着更快的性能大大缓解了它。
Toolsmythe

30

从LINQ上下文中剥离下来,这归结为如何确定文件是否与扩展名列表匹配。System.IO.Path.GetExtension()String.EndsWith()。是一个更好的选择。倍数||可以替换为.Contains().IndexOf()取决于集合。

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

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

2
*如果要与它们进行字符串比较,则需要删除。
加布

另一个建议是使用允许不区分大小写的检查的重载。
Dirk Vollmar 2010年

1
使用Hashset和不区分大小写的比较可能更好。
Jim Mischel 2010年

2
您需要包括点(.mp3)。使用string.ToLower()处理大小写。
汉斯·帕桑

@Hans,关于点,但是ToLower()会比OrdinalIgnoreCase更好吗?刚一挑:stackoverflow.com/questions/501906/...
亨克Holterman


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

您还需要“ .mp3”,而不是“ mp3”。
Henk Holterman 2010年

谢谢工作完美...就我而言,我需要在.Where之前添加.ToArray(),如果没有此LINQ查询将无法正常工作。
Equiman

12

正如我在评论中指出的那样,尽管Mikael Svenson的辅助方法是很少的解决方案,但是如果您想尽快为一次性项目做某事,请考虑使用Linq extension .Union()。这使您可以将两个可枚举的序列连接在一起。在您的情况下,代码如下所示:

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

这将在一行中创建并填充您的结果列表。


2
优雅,并且避免了C#枚举所有文件,从而允许文件系统尽可能地进行优化。
Craig Brunetti

2

我知道这是一篇过时的文章,但是我想出了一个人们可能喜欢使用的解决方案。

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

1

对于使用与GUI打开对话框相同的文件扩展名列表字符串进行过滤,例如:

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

包装:

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

0

我这样解决了这个问题:

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

foreach (var file in Directory.EnumerateFiles(folder, "*.*", SearchOption.AllDirectories).Where(x => formats.Any(x.EndsWith)))
{
    // TODO...
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.