
Как сохранить Collection в текстовый файл на VB6 и загрузить обратно
Collection в VB6 удобна как список строк в памяти: имена файлов, ошибки, результаты проверки. Рано или поздно её нужно сохранить на диск. Разберём функцию, которая делает это аккуратно.
Задача и требования к функции
Типичный сценарий: программа обходит папку или базу и складывает найденное в Collection, а в конце нужно получить отчёт, который можно открыть Блокнотом или подгрузить при следующем запуске. Формат самый простой: один элемент на строку, в том же порядке, что и в коллекции.
Сформулируем, что должна уметь функция, чтобы её можно было переиспользовать во всех проектах:
- создавать новый файл или дописывать в конец существующего, режим задаётся параметром;
- сохранять порядок элементов;
- не падать на пустой коллекции и на Nothing;
- сообщать вызывающему коду об ошибке с понятным текстом, а не молча терять данные.
Версия на FileSystemObject
FileSystemObject (ссылка Microsoft Scripting Runtime) даёт удобный режим ForAppending. Встречается вариант, в котором для дозаписи файл сначала целиком читают в строку, а затем перезаписывают вместе с новыми строками. Это лишняя работа и риск: если программа упадёт посередине, старое содержимое будет потеряно. Режим дозаписи решает задачу без этого.
Третий параметр OpenTextFile, равный True, создаёт файл, если его ещё нет. Перебор идёт через For Each по Variant, так как Collection хранит значения как Variant.
' Требуется ссылка: 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Что делать с нестроковыми элементами
В Collection можно положить что угодно: числа, даты, Null и даже объекты. Запись объекта через WriteLine вызовет ошибку 438 или 13, а Null превратится в ошибку 94. Даты и дробные числа запишутся в региональном формате, и файл, сохранённый на русской Windows, прочитается иначе на английской.
Поэтому преобразование вынесено в отдельную функцию ItemToText. Она приводит дату к ISO-формату, число к виду с точкой, а для объектов пытается взять свойство по умолчанию. Заодно она заменяет переводы строк внутри значения, иначе один элемент превратится в несколько строк файла и при обратной загрузке коллекция разъедется.
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-объектов.
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 восстанавливает многострочные значения. Пустые строки пропускаем: они появляются, например, из-за финального перевода строки, добавленного текстовым редактором.
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, куда у пользователя нет прав записи;
- если файл читают другие программы во время записи, пишите во временный файл и переименовывайте его по окончании.
Как это делают сегодня
Вопросы и ответы
Почему мой файл после дозаписи начинается с пустой строки?
Обычно это остаток старого способа: содержимое читалось в строку и записывалось через WriteLine, добавлявший лишний перевод строки. Режим ForAppending или Append такой проблемы не создаёт.
Можно ли сохранить ключи Collection вместе со значениями?
Нет, Collection в VB6 не позволяет перебрать ключи. Если они нужны, храните данные в Scripting.Dictionary: у неё есть методы Keys и Items.
Что быстрее, Print # или TextStream.WriteLine?
Print # работает без COM-вызовов и на десятках тысяч строк заметно быстрее. Разница важна только для больших коллекций, на сотнях элементов её не видно.