Как посчитать количество файлов в папке на VB.NET

РазделVB.NET и WinFormsУровеньНовичок Чтение7 минОпубликовано ТегиVB.NETФайловая системаSystem.IO

Посчитать файлы в папке на VB.NET можно одной строкой. Но на большом диске эта строка либо съест память, либо упадёт на первой закрытой папке. Разбираем, как считать надёжно.

Главное. Для маленькой папки хватает Directory.GetFiles(path).Length. Для большого дерева используйте EnumerateFiles, обходите папки через стек с перехватом ошибок по каждой папке и выполняйте подсчёт в фоне.

Одна строка для простого случая

В VB6 для подсчёта файлов приходилось крутить цикл с функцией Dir или обходить коллекцию Files объекта FileSystemObject. В VB.NET класс System.IO.Directory возвращает массив имён файлов, и количество равно длине этого массива.

Для папки с сотней файлов этого вполне достаточно. Функцию удобно положить в модуль и вызывать из любой формы, например MsgBox(GetFileCount(«C:\Temp»)). Метод GetFiles считает только файлы, вложенные папки в результат не попадают.

FileCount.vbVB.NET
Imports System.IO

Public Module FileCount
    ' Количество файлов в папке без вложенных
    Public Function GetFileCount(path As String) As Integer
        Return Directory.GetFiles(path).Length
    End Function

    ' Количество файлов по маске, например "*.jpg"
    Public Function GetFileCount(path As String, mask As String) As Integer
        Return Directory.GetFiles(path, mask).Length
    End Function
End Module
Не заворачивайте вызов в Try с повторным Throw New Exception: так теряется тип исключения. Пусть вызывающий код сам решит, что делать с DirectoryNotFoundException или UnauthorizedAccessException.

Подсчёт с вложенными папками

Чтобы посчитать файлы во всём дереве, рекурсию писать не нужно: у GetFiles есть третий параметр SearchOption.AllDirectories. Вызов Directory.GetFiles(path, «*.*», SearchOption.AllDirectories).Length вернёт общее количество файлов во всех подпапках.

У этого варианта две серьёзные проблемы. Первая: метод сначала строит полный массив строк и только потом отдаёт его. Для диска с миллионом файлов это сотни мегабайт памяти и долгая пауза, во время которой интерфейс висит. Вторая: если хотя бы одна подпапка закрыта правами, весь вызов падает с UnauthorizedAccessException, и вы не получаете вообще ничего.

EnumerateFiles: считаем на лету

В .NET Framework 4 появился метод Directory.EnumerateFiles. Он возвращает последовательность, которая читает файловую систему порциями по мере перебора. Массив целиком не строится, первые результаты приходят сразу, а память почти не расходуется.

Для подсчёта достаточно пройти последовательность и увеличить счётчик. Метод Count из LINQ делает ровно это. Обратите внимание на тип Long: на файловом сервере количество файлов легко выходит за пределы Integer.

FileCountLazy.vbVB.NET
Imports System.IO
Imports System.Linq

Public Module FileCountLazy
    ' .NET 4+: обход без построения массива
    Public Function CountAll(path As String) As Long
        Return Directory.EnumerateFiles(path, "*",
            SearchOption.AllDirectories).LongCount()
    End Function
End Module

Обход закрытых папок

EnumerateFiles решает проблему памяти, но не проблему прав: встретив папку System Volume Information или чужой профиль, перечисление тоже бросит исключение и остановится. Если нужно посчитать всё, до чего программа может дотянуться, придётся обходить дерево самостоятельно и ловить ошибки по каждой папке отдельно.

Вместо рекурсии удобно использовать стек: так глубокая структура каталогов не приведёт к переполнению стека вызовов, а код остаётся коротким. Папки, которые не удалось прочитать, можно собрать в список и показать пользователю.

SafeFileCount.vbVB.NET
Imports System.IO
Imports System.Collections.Generic

Public Module SafeFileCount
    ' Считает файлы во всём дереве, пропуская недоступные папки
    Public Function CountFilesSafe(root As String, skipped As List(Of String)) As Long
        Dim total As Long = 0
        Dim pending As New Stack(Of String)
        pending.Push(root)
        While pending.Count > 0
            Dim dir = pending.Pop()
            Try
                total += Directory.EnumerateFiles(dir).LongCount()
                For Each subDir In Directory.EnumerateDirectories(dir)
                    pending.Push(subDir)
                Next
            Catch ex As UnauthorizedAccessException
                skipped.Add(dir) ' нет прав, идём дальше
            Catch ex As PathTooLongException
                skipped.Add(dir) ' путь длиннее 260 символов
            End Try
        End While
        Return total
    End Function
End Module

Маски и скрытые файлы

Маска в GetFiles и EnumerateFiles работает почти как в командной строке, но с одной исторической странностью. Если в маске расширение из трёх символов, например «*.htm», будут найдены и файлы .html, потому что Windows сравнивает ещё и короткие имена 8.3. Если важна точность, фильтруйте результат дополнительно по Path.GetExtension.

Скрытые и системные файлы методы возвращают наравне с обычными. Проводник по умолчанию их не показывает, поэтому числа могут не совпасть с тем, что видит пользователь. Чтобы считать как Проводник, проверяйте атрибуты через File.GetAttributes или используйте DirectoryInfo.EnumerateFiles и свойство Attributes у FileInfo.

  • thumbs.db и desktop.ini: типичные скрытые файлы, которые раздувают счётчик;
  • FileAttributes.Hidden и FileAttributes.System проверяются побитовым And;
  • FileInfo уже содержит атрибуты, лишнего обращения к диску не будет.

Не блокируйте интерфейс

Подсчёт большого дерева на сетевом диске может занимать минуты. Если вызвать его в обработчике кнопки, форма перестанет отвечать, и Windows пометит программу как зависшую. Выносите обход в BackgroundWorker или отдельный поток и сообщайте прогресс, например текущую папку и промежуточный счётчик.

Как это делают сегодня

В .NET Core 2.1 и новее появился класс EnumerationOptions: параметры RecurseSubdirectories = True и IgnoreInaccessible = True позволяют одной строкой обойти дерево и пропустить закрытые папки, а MatchType.Simple отключает странности с короткими именами. Подсчёт в WinForms запускают через Await Task.Run, а прогресс передают через IProgress(Of T).

Вопросы и ответы

Почему GetFiles с маской *.htm находит файлы .html?

Windows сравнивает маску и с длинным, и с коротким именем файла 8.3, а короткое расширение у .html выглядит как .HTM. Фильтруйте результат по Path.GetExtension, если нужна точность.

Как посчитать файлы и папки вместе?

Используйте Directory.EnumerateFileSystemEntries: метод возвращает и файлы, и подкаталоги. Для раздельного подсчёта вызовите EnumerateFiles и EnumerateDirectories.

Сколько памяти занимает GetFiles на большом диске?

Каждая строка пути занимает около сотни байт и больше, поэтому миллион файлов даёт сотни мегабайт. EnumerateFiles читает каталог порциями и почти не расходует память.

Читайте также