Как сохранить Collection в текстовый файл на VB6 и загрузить обратно

РазделVisual Basic 6УровеньНовичок Чтение7 минОпубликовано ТегиVB6ФайлыCollection

Collection в VB6 удобна как список строк в памяти: имена файлов, ошибки, результаты проверки. Рано или поздно её нужно сохранить на диск. Разберём функцию, которая делает это аккуратно.

Главное. Для сохранения Collection используйте режимы ForAppending или Append вместо чтения и перезаписи всего файла, а нестроковые элементы явно приводите к тексту с фиксированным форматом. Тогда файл читается обратно без потерь.

Задача и требования к функции

Типичный сценарий: программа обходит папку или базу и складывает найденное в Collection, а в конце нужно получить отчёт, который можно открыть Блокнотом или подгрузить при следующем запуске. Формат самый простой: один элемент на строку, в том же порядке, что и в коллекции.

Сформулируем, что должна уметь функция, чтобы её можно было переиспользовать во всех проектах:

  • создавать новый файл или дописывать в конец существующего, режим задаётся параметром;
  • сохранять порядок элементов;
  • не падать на пустой коллекции и на Nothing;
  • сообщать вызывающему коду об ошибке с понятным текстом, а не молча терять данные.

Версия на FileSystemObject

FileSystemObject (ссылка Microsoft Scripting Runtime) даёт удобный режим ForAppending. Встречается вариант, в котором для дозаписи файл сначала целиком читают в строку, а затем перезаписывают вместе с новыми строками. Это лишняя работа и риск: если программа упадёт посередине, старое содержимое будет потеряно. Режим дозаписи решает задачу без этого.

Третий параметр OpenTextFile, равный True, создаёт файл, если его ещё нет. Перебор идёт через For Each по Variant, так как Collection хранит значения как Variant.

modCollection.basVB6
' Требуется ссылка: Microsoft Scripting Runtime
Public Sub CollectionToFile(ByVal colLines As Collection, _
                            ByVal strPath As String, _
                            Optional ByVal blnOverwrite As Boolean = True)
    Dim fso As New FileSystemObject
    Dim ts As TextStream
    Dim varItem As Variant
    On Error GoTo ErrTrap
    If colLines Is Nothing Then Exit Sub
    ' ForWriting перезаписывает файл, ForAppending дописывает в конец
    Set ts = fso.OpenTextFile(strPath, _
             IIf(blnOverwrite, ForWriting, ForAppending), True)
    For Each varItem In colLines
        ts.WriteLine ItemToText(varItem)
    Next
    ts.Close
    Exit Sub
ErrTrap:
    Dim lngErr As Long, strErr As String
    lngErr = Err.Number: strErr = Err.Description   ' сохраняем до Close
    If Not ts Is Nothing Then ts.Close
    Err.Raise lngErr, "CollectionToFile", _
              "Не удалось записать " & strPath & ": " & strErr
End Sub
Номер и описание ошибки сохраняются в переменные до вызова Close: любой успешный метод COM-объекта может сбросить объект Err, и наружу уйдёт пустая ошибка.

Что делать с нестроковыми элементами

В Collection можно положить что угодно: числа, даты, Null и даже объекты. Запись объекта через WriteLine вызовет ошибку 438 или 13, а Null превратится в ошибку 94. Даты и дробные числа запишутся в региональном формате, и файл, сохранённый на русской Windows, прочитается иначе на английской.

Поэтому преобразование вынесено в отдельную функцию ItemToText. Она приводит дату к ISO-формату, число к виду с точкой, а для объектов пытается взять свойство по умолчанию. Заодно она заменяет переводы строк внутри значения, иначе один элемент превратится в несколько строк файла и при обратной загрузке коллекция разъедется.

modCollection.basVB6
Private Function ItemToText(ByVal varItem As Variant) As String
    If IsObject(varItem) Then
        On Error Resume Next
        ItemToText = CStr(varItem)       ' свойство по умолчанию, если есть
        If Err.Number <> 0 Then ItemToText = "[" & TypeName(varItem) & "]"
        On Error GoTo 0
    ElseIf IsNull(varItem) Then
        ItemToText = ""
    ElseIf VarType(varItem) = vbDate Then
        ItemToText = Format$(varItem, "yyyy-mm-dd hh:nn:ss")
    ElseIf IsNumeric(varItem) And VarType(varItem) <> vbString Then
        ItemToText = Str$(varItem)       ' всегда с точкой
        ItemToText = LTrim$(ItemToText)
    Else
        ItemToText = CStr(varItem)
    End If
    ' перевод строки внутри элемента заменяем на \n
    ItemToText = Replace(Replace(ItemToText, vbCrLf, "\n"), vbLf, "\n")
End Function

Версия без внешних ссылок

Если проект не должен зависеть от Scripting Runtime (например, на целевых машинах администраторы отключают scrrun.dll), ту же задачу решают встроенные операторы. Режимы Output и Append соответствуют перезаписи и дозаписи, оператор Print # с точкой с запятой в конце не нужен: без неё он сам добавляет vbCrLf.

Этот вариант ещё и быстрее на больших коллекциях, потому что не создаёт COM-объектов.

modCollection.basVB6
Public Sub CollectionToFileNative(ByVal colLines As Collection, _
                                  ByVal strPath As String, _
                                  Optional ByVal blnOverwrite As Boolean = True)
    Dim intFile As Integer, varItem As Variant
    If colLines Is Nothing Then Exit Sub
    intFile = FreeFile
    If blnOverwrite Then
        Open strPath For Output As #intFile
    Else
        Open strPath For Append As #intFile
    End If
    For Each varItem In colLines
        Print #intFile, ItemToText(varItem)
    Next
    Close #intFile
End Sub

Обратная операция: файл в Collection

Сохранять имеет смысл, только если файл можно прочитать обратно. Line Input # читает строку без завершающего перевода, а обработка \n восстанавливает многострочные значения. Пустые строки пропускаем: они появляются, например, из-за финального перевода строки, добавленного текстовым редактором.

modCollection.basVB6
Public Function FileToCollection(ByVal strPath As String) As Collection
    Dim col As New Collection, intFile As Integer, strLine As String
    Set FileToCollection = col
    If Len(Dir$(strPath)) = 0 Then Exit Function
    intFile = FreeFile
    Open strPath For Input As #intFile
    Do Until EOF(intFile)
        Line Input #intFile, strLine
        If Len(strLine) > 0 Then col.Add Replace(strLine, "\n", vbCrLf)
    Loop
    Close #intFile
End Function

Кодировка и практические советы

Оба способа пишут файл в ANSI, то есть в кодовой странице системы. Для отчёта, который откроют на той же машине, этого достаточно. Если файл уходит в другие системы или содержит символы вне windows-1251, откройте TextStream с параметром TristateTrue (получится UTF-16) или пишите через ADODB.Stream с Charset = "utf-8".

Пара привычек, которые экономят время при сопровождении:

  • передавайте полный путь, а не имя файла: текущая папка процесса меняется после диалогов открытия файла;
  • для временных отчётов используйте папку из Environ$("TEMP"), а не каталог программы в Program Files, куда у пользователя нет прав записи;
  • если файл читают другие программы во время записи, пишите во временный файл и переименовывайте его по окончании.

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

В VB.NET вместо Collection используют List(Of String), а запись сводится к File.WriteAllLines(path, list, Encoding.UTF8) или File.AppendAllLines. Если в списке не только строки, проще сериализовать его в JSON через System.Text.Json: формат сам сохранит типы, переводы строк и порядок элементов.

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

Почему мой файл после дозаписи начинается с пустой строки?

Обычно это остаток старого способа: содержимое читалось в строку и записывалось через WriteLine, добавлявший лишний перевод строки. Режим ForAppending или Append такой проблемы не создаёт.

Можно ли сохранить ключи Collection вместе со значениями?

Нет, Collection в VB6 не позволяет перебрать ключи. Если они нужны, храните данные в Scripting.Dictionary: у неё есть методы Keys и Items.

Что быстрее, Print # или TextStream.WriteLine?

Print # работает без COM-вызовов и на десятках тысяч строк заметно быстрее. Разница важна только для больших коллекций, на сотнях элементов её не видно.

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