97

I have the following 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);                 
       }

It works fine and does what I need. Except for one small thing. I would like to find a better way to filter on multiple extensions. I would like to use a string array with filters such as this:

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

What is the most efficient way to do this using NET Framework 4.0/LINQ? Any suggestions?

I'd appreciate any help being an occasional programmer :-)

2
  • You should consider running each extension search in parallel. I created some useful helper methods in my answer. One which takes a regexp, and one which takes a string list. Commented Sep 20, 2010 at 18:51
  • 2
    This is a very old question (already suitably answered by @MikaelSvenson ), but another option is to use the Enumerable extension .Union(), like so: foreach (var file in Directory.EnumerateFiles(path, ".mp3", SearchOption.AllDirectories).Union(Directory.EnumerateFiles(path, ".wma", SearchOption.AllDirectories)) { ... } Commented Mar 24, 2015 at 20:44

11 Answers 11

97

I created some helper methods to solve this which I blogged about earlier this year.

One version takes a regex pattern \.mp3|\.mp4, and the other a string list and runs in parallel.

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));
   }
}
Sign up to request clarification or add additional context in comments.

16 Comments

Thanks for a good implementation. What can be a good(efficient) way to finally show the results on WPF screen? I plan to use your parallel method to get files. What if i use foreach to iterate the results and store them in a List, and them load them on screen?
You can just bind to the output of either methods as the binding will enumerate all results for you. No need to store it in a separate list first. The most efficient way is to start displaying items as they are enumerated. I'm no WPF expert, but I guess you should be able to render per item with some signalling.
Great examples! Just to note a few characteristics of each of the two methods... With the PARALLEL method, Searches are NOT case sensitive, and the results you will get is going to be out of order. With the REGEX method, Searches ARE case sensitive (unless you use something like "(?i)\.mp3$|\.mp4$"), and the results you will get will be in order as you would expect. I have ran tests and noticed that the parallel version might run a SLIGHT bit faster but all and all it a VERY small difference.
@ArvoBowen good catch on the case sensitive comparison, and added a regexoption in the code
This is a great solution; thanks! Just an FYI: I ran into performance issues that I traced back to IEnumerable (mostly centered around the Count( ) method which I used in a few places, but that was not the only performance hit). My list had about 4700 file names. I wound up doing a .ToArray( ) on the list and dealing with everything as an array; you pay a one-time price turning the list into an array but it is more than mitigated by the noticeably faster performance thereafter.
|
37

The most elegant approach is probably:

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

But it might not be the most efficient.

4 Comments

How to include subfolders?
@AsenKasimov The EnumerateFiles method has an overload with a parameter that let's you specify to also search inside subfolders.
Nice point, but need a brief edit: masks.SelectMany(m => dinf.EnumerateFiles(m));.
With subfolders: var files = masks.SelectMany(m=>directory.EnumerateFiles(m,new EnumerationOptions() { RecurseSubdirectories=true}));
23
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);
}

2 Comments

You also need ".mp3", not "mp3".
Thanks works perfect... in my case I need add .ToArray() before .Where ... without this LINQ Query doesn't work.
16

As I noted in a comment, while Mikael Svenson's helper methods are great little solutions, if you're ever trying to do something for a one-off project in a hurry again, consider the Linq extension .Union( ). This allows you to join together two enumerable sequences. In your case, the code would look like this:

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

This creates and fills your result list all in one line.

2 Comments

Elegant, and avoids enumeration of all files by C#, allowing the file system to optimize however it can.
Probably it's slower to do two(or more) searches with extension pattern each than do a generic search returning all files and filter by extension in C#. Would be interesting to benchmark though.
7

I solved this problem this way:

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

foreach (var file in Directory.EnumerateFiles(folder, "*.*", SearchOption.AllDirectories).Where(x => formats.Any(x.EndsWith)))
{
    // TODO...
}

Comments

2

I know this is an old post but I came up with a solution people might like to use.

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

Comments

1

For Filtering using the same File Extensions list strings as GUI Open Dialogs e.g.:

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

Packaged up:

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

Comments

1

Beginning from the NET Core 2.1 and .NET Standard 2.1 there is built-in class FileSystemName: documentation, source code which provides methods for matching file system names:

Example:

public static IEnumerable<string> EnumerateFiles(string path, string[] searchPatterns, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
    return Directory.EnumerateFiles(path, "*", searchOption)
                    .Where(fileName => searchPatterns.Any(pattern => FileSystemName.MatchesSimpleExpression(pattern, fileName)));
}

I've adapted the existing source code of FileSystemName to be used in .NetFramework 4: Gist FileSystemName for .NetFramework 4.

Comments

0

To filter using a Regex pattern and file Modified date.

In the example it return files that:

  • name starts with Bck for extensions tgz and xml
  • are older than 200 days.

Returns Full path name


string startPath = @"c:\temp";
int olderThanDays = 200;

Regex re = new Regex($@"^Bck.*\.tgz|^Bck.*\.xml");
var files = new DirectoryInfo(startPath)
    .EnumerateFiles($"*.*", SearchOption.AllDirectories)
    .Where(f => f.CreationTime < DateTime.Now.AddDays(-olderThanDays) && re.IsMatch(f.Name))             
    .Select(f => f.FullName).ToList();

Comments

0

Yevhan's answer is by far the cleanest and most modern, but I would take it one step further and create a FileSystemEnumerable<FileInfo> directly. This will give you the added benefit of being able to provide your own transform (to in this case get a FileInfo object rather than a file path), and specify a ShouldIncludePredicate predicate to execute all your desired filters at once, with out needing multiple enumerations.

    var path = @"C:\SEARCH\DIRECTORY";

    IReadOnlyList<string> filters = [
        "*.xlsx",
        "*.xls"
    ];

    bool FilterPrediate(ref FileSystemEntry f)
    {
        if (f.IsDirectory)
            return false;

        foreach (var filter in filters)
            if (FileSystemName.MatchesSimpleExpression(filter, f.FileName))
                return true;

        return false;
    }

    var enumerable = new FileSystemEnumerable<FileInfo>(path, (ref FileSystemEntry f) => (FileInfo)f.ToFileSystemInfo())
    {
        ShouldIncludePredicate = FilterPrediate
    };

    foreach (FileInfo file in enumerable)
    {
        // TODO do work
    }

Comments

0

This is the simplest solution I could think of:

var tracks = di.EnumerateFiles("*.mp3").ToList();
tracks.AddRange(di.EnumerateFiles("*.wma"));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.