Отличный вопрос! Давайте подробно разберем рекурсивный поиск файлов в C# и проанализируем возможные конструкции.
Базовые подходы к рекурсивному поиску
1. Классический рекурсивный метод
public static List<string> FindFilesRecursive(string rootPath, string pattern = "*") { var results = new List<string>(); try { // Добавляем файлы из текущей директории results.AddRange(Directory.GetFiles(rootPath, pattern)); // Рекурсивно обходим поддиректории foreach (string subDirectory in Directory.GetDirectories(rootPath)) { results.AddRange(FindFilesRecursive(subDirectory, pattern)); } } catch (UnauthorizedAccessException) { // Обработка отсутствия прав доступа Console.WriteLine($"Нет доступа к: {rootPath}"); } catch (DirectoryNotFoundException) { // Обработка несуществующей директории Console.WriteLine($"Директория не найдена: {rootPath}"); } return results; } // Использование var files = FindFilesRecursive(@"C:MyProject", "*.txt");
2. Использование Stack (итеративный подход)
public static List<string> FindFilesIterative(string rootPath, string pattern = "*") { var results = new List<string>(); var stack = new Stack<string>(); stack.Push(rootPath); while (stack.Count > 0) { string currentPath = stack.Pop(); try { // Добавляем файлы results.AddRange(Directory.GetFiles(currentPath, pattern)); // Добавляем поддиректории в стек foreach (string subDirectory in Directory.GetDirectories(currentPath)) { stack.Push(subDirectory); } } catch (UnauthorizedAccessException) { Console.WriteLine($"Нет доступа к: {currentPath}"); } } return results; }
Анализ возможных конструктов
Будет ли работать такой код?
// Пример 1: Простая рекурсия public void FindFiles(string path) { foreach (var file in Directory.GetFiles(path)) Console.WriteLine(file); foreach (var dir in Directory.GetDirectories(path)) FindFiles(dir); // Рекурсивный вызов }
Ответ: Да, будет работать, но с ограничениями:
- Нет обработки исключений
- Риск StackOverflowException при глубокой вложенности
- Нет фильтрации по шаблону
Потенциальные проблемы и их решение
1. Исключения доступа
public static IEnumerable<string> SafeFindFiles(string rootPath, string pattern = "*") { // Обработка исключений для каждой директории IEnumerable<string> files = Enumerable.Empty<string>(); IEnumerable<string> directories = Enumerable.Empty<string>(); try { files = Directory.GetFiles(rootPath, pattern); directories = Directory.GetDirectories(rootPath); } catch (UnauthorizedAccessException) { yield break; } catch (DirectoryNotFoundException) { yield break; } foreach (var file in files) yield return file; foreach (var directory in directories) { foreach (var file in SafeFindFiles(directory, pattern)) yield return file; } }
2. Глубокая рекурсия
public static List<string> FindFilesWithDepthLimit(string path, string pattern = "*", int maxDepth = 10) { var results = new List<string>(); FindFilesWithDepthLimit(path, pattern, maxDepth, 0, results); return results; } private static void FindFilesWithDepthLimit(string path, string pattern, int maxDepth, int currentDepth, List<string> results) { if (currentDepth > maxDepth) return; try { results.AddRange(Directory.GetFiles(path, pattern)); foreach (var dir in Directory.GetDirectories(path)) { FindFilesWithDepthLimit(dir, pattern, maxDepth, currentDepth + 1, results); } } catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException) { // Игнорируем проблемные директории } }
Современные подходы в .NET
1. Использование EnumerationOptions ( .NET 5+ )
public static IEnumerable<string> FindFilesModern(string rootPath, string pattern = "*") { var options = new EnumerationOptions { RecurseSubdirectories = true, IgnoreInaccessible = true, ReturnSpecialDirectories = false, MaxRecursionDepth = 32 }; return Directory.EnumerateFiles(rootPath, pattern, options); }
2. Асинхронный поиск
public static async Task<List<string>> FindFilesAsync(string rootPath, string pattern = "*") { var results = new List<string>(); await FindFilesAsync(rootPath, pattern, results); return results; } private static async Task FindFilesAsync(string path, string pattern, List<string> results) { await Task.Run(() => { try { results.AddRange(Directory.GetFiles(path, pattern)); var directories = Directory.GetDirectories(path); var tasks = directories.Select(dir => FindFilesAsync(dir, pattern, results)); Task.WaitAll(tasks.ToArray()); } catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException) { // Логируем, но продолжаем работу } }); }
Лучшие практики
1. Использование EnumerateFiles вместо GetFiles
// Плохо - загружает все файлы в память сразу string[] allFiles = Directory.GetFiles(path, "*", SearchOption.AllDirectories); // Хорошо - ленивое перечисление IEnumerable<string> files = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories);
2. Обработка разных сценариев
public static class FileSearcher { public static SearchResult FindFiles(SearchOptions options) { var result = new SearchResult(); FindFiles(options.RootPath, options, result, 0); return result; } private static void FindFiles(string path, SearchOptions options, SearchResult result, int depth) { if (depth > options.MaxDepth) return; try { // Поиск файлов var files = Directory.EnumerateFiles(path, options.Pattern); foreach (var file in files) { if (options.Filter == null || options.Filter(file)) { result.FoundFiles.Add(file); } } // Рекурсивный обход директорий if (options.IncludeSubdirectories) { foreach (var dir in Directory.EnumerateDirectories(path)) { FindFiles(dir, options, result, depth + 1); } } } catch (Exception ex) { result.Errors.Add(new SearchError { Path = path, Exception = ex }); } } } public class SearchOptions { public string RootPath { get; set; } public string Pattern { get; set; } = "*"; public bool IncludeSubdirectories { get; set; } = true; public int MaxDepth { get; set; } = int.MaxValue; public Func<string, bool> Filter { get; set; } } public class SearchResult { public List<string> FoundFiles { get; } = new(); public List<SearchError> Errors { get; } = new(); }
Вывод
Да, рекурсивный поиск файлов в C# будет работать, но важно учитывать:
- Обработку исключений - особенно
UnauthorizedAccessException
- Глубину рекурсии - риск
StackOverflowException
- Производительность - используйте
EnumerateFiles
вместоGetFiles
- Гибкость - предусмотрите различные сценарии использования
Для большинства случаев в современных версиях .NET рекомендуется использовать встроенные методы с EnumerationOptions
, а для сложных сценариев - реализовывать собственные решения с учетом описанных лучших практик.