Як фільтрувати Directory.EnumerateFiles з кількома критеріями?


79

У мене є такий код:

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 (файл var у каталозі Directory.EnumerateFiles (шлях, " .mp3", SearchOption). AllDirectories) .Union (Directory.EnumerateFiles (path, " .wma", SearchOption.AllDirectories)) {...}
Kirkaiya

Відповіді:


86

Я створив кілька допоміжних методів, щоб вирішити цю проблему, про що я писав в блогах на початку цього року.

Одна версія приймає шаблон регулярного виразу \.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 для ітерації результатів та збереження їх у списку, а вони завантажують їх на екран?
Саураб Кумар,

Ви можете просто прив’язати до виводу будь-якого з методів, оскільки прив’язка буде перераховувати всі результати для вас. Не потрібно спочатку зберігати його в окремому списку. Найефективніший спосіб - почати відображати елементи у міру їх перерахування. Я не фахівець WPF, але, мабуть, ви повинні мати можливість рендерінгу за елемент із певними сигналами.
Mikael Svenson

Чудові приклади! Просто зазначимо кілька характеристик кожного з двох методів ... За допомогою цього PARALLELметоду Пошуки НЕ чують регістр, і результати, які Ви отримаєте, будуть неправильними. За допомогою REGEXметоду "Пошуки" ЧУТЬ регістр (якщо ви не використовуєте щось подібне "(?i)\.mp3$|\.mp4$"), і результати, які ви отримаєте, будуть у порядку, як і слід було очікувати. Я провів тести і помітив, що паралельна версія може працювати СЛУЧАЙШЕ швидше, але все і все це ДУЖЕ невелика різниця.
Арво Боуен,

@ArvoBowen хороший випадок порівняння з урахуванням регістру та додав у код регулярне вираз
Mikael Svenson

Це чудове рішення; Дякую! Просто FYI: Я зіткнувся з проблемами продуктивності, які я прослідкував до IEnumerable (в основному, зосередженим навколо методу Count (), який я використовував у кількох місцях, але це був не єдиний хіт продуктивності). У моєму списку було близько 4700 імен файлів. Я закінчив робити .ToArray () у списку і мав справу з усім як з масивом; Ви платите одноразову ціну, перетворюючи список на масив, але це більш ніж пом'якшується помітно швидшою продуктивністю в подальшому.
Інструментиміф

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
Вам потрібно видалити *s, якщо ви хочете зробити порівняння рядків з ними.
Гейб,

І ще однією пропозицією було б використовувати перевантаження, яке дозволяє проводити перевірку, не враховуючи регістр.
Dirk Vollmar

1
Можливо, краще використовувати хешсет і порівняння, що не враховує регістр.
Джим Мішель,

2
Вам потрібно включити крапку (.mp3). Використовуйте string.ToLower () для обробки регістру.
Ганс Пассант,

@Hans, правильно щодо крапки, але чи буде ToLower () кращим за OrdinalIgnoreCase? Просто вибір: stackoverflow.com/questions/501906/…
Хенк Холтерман

19

Найелегантніший підхід, мабуть:

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

Але це може бути не найефективніше.


Як включити підпапки?
Асен

1
@AsenKasimov Метод EnumerateFiles має перевантаження параметром, який дозволить вам вказати для пошуку в підпапках.
Том Пажурек

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

Подяка працює ідеально ... у моєму випадку мені потрібно додати .ToArray () раніше. Де ... без цього запиту LINQ не працює.
еквіман

12

Як я зазначив у коментарі, хоча допоміжні методи Мікаеля Свенсона є чудовими маленькими рішеннями, якщо ви коли-небудь знову намагаєтесь щось зробити для одноразового проекту, поспішайте, розгляньте розширення Linq .Union () . Це дозволяє об’єднати дві незліченні послідовності. У вашому випадку код буде виглядати так:

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

Це створює та заповнює ваш список результатів одним рядком.


2
Елегантний і уникає перерахування всіх файлів за допомогою C #, дозволяючи файловій системі оптимізувати, наскільки це можливо.
Крейг Брунетті,

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

Для фільтрації з використанням тих самих рядків списку розширень файлів, що й діалогові вікна відкритого графічного інтерфейсу, наприклад:

".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...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.