В прежних версиях Visual Basic использовались разные средства обработки данных, причем
выбор определялся типом источника данных. Например, обработка данных, прочитанных
из файла на диске, принципиально отличалась от обработки данных, загруженных
из Интернета. Эти времена остались в прошлом. Одной из целей, поставленных при
проектировании .NET Framework, было обеспечение унифицированного механизма обработки
данных, не зависящего от источника.
Центральное
место в этом механизме занимает понятие потока (stream). Термин «поток»
в данном случае происходит от выражения «поток байтов». Собственно,
все данные, с которыми работает компьютер, — числа, текст и графика — сводятся
к обычной последовательности байтов. Таким образом, подход, избранный проектировщиками
.NET Framework, выглядит вполне логично — они разработали абстрактный класс,
выполняющий обобщенные операции с данными. Наличие абстрактного класса упрощает
программирование ввода-вывода в VB .NET и выявляет сходство между разнородными
операциями. Короче говоря, абстрактный класс Stream, помеченный ключевым словом
Mustlnherit, является идеальной базой для построения объектно-ориентированной
иерархии ввода-вывода.
При работе
с потоками данных, поступающих с клавиатуры, из памяти, из файла или
сетевого соединения, программисты .NET используют классы, адаптированные для
конкретных источников. Но прежде, чем рассматривать эти типы потоков, необходимо
познакомиться с тем, как организовано локальное хранение данных пользователем.
По этой причине мы начинаем эту главу с описания операций с файлами и каталогами,
а затем рассмотрим существующие разновидности потоков .NET, в том числе файловые,
сетевые и объектные потоки, позволяющие сохранять объекты на диске, и другие
источники данных.
Глава завершается
примером использования новых серверных средств RAD (Rapid Application Development)
для написания простого монитора файловой системы. Программа следит за изменениями
в каталогах (такими, как создание и удаление файлов) и обрабатывает различные
события, инициируемые в зависимости от типа изменений. В предыдущих версиях
VB написание подобных программ было сильно затруднено тем, что программисту
приходилось использовать Windows API весьма нетривиальным образом. И хотя в
этой книге мы не сможем сколько-нибудь полно описать RAD-иногоументарий VB .NET,
мы надеемся, что это подстегнет ваше любопытство и заставит подробнее изучить
этот чрезвычайно полезный аспект VB .NET.
Данная
глава познакомит читателя с основными принципами ввода-вывода в .NET, однако
она не претендует на полное изложение этой обширной темы. О вводе-выводе вполне
можно написать отдельную книгу — как и серверном RAD-инструментарии!
В VB .NET
существуют два класса для работы с каталогами и два класса для работы с файлами.
Обращение
к функциональным возможностям классов Directory и File происходит при помощи
общих методов. Поскольку методы классов Di rectory и Fi1е являются общими, они
могут вызываться и без предварительного создания экземпляра оператором New.
Конечно, это повышает их эффективность при разовых обращениях к конкретному
файлу или каталогу. Тем не менее при многократном обращении к файлу или каталогу
эти методы становятся менее эффективными. Классы Di rectorylnfo и Filelnfo содержат
обычные методы, поэтому обращение к их членам происходит через конкретные экземпляры.
Другое различие
между этими парами заключается в том, что классы Directory и File являются производными
непосредственно от Object, а классы Directory-Info и FileInfo объявлены производными
от абстрактного (Mustlnherit) класса FileSystemInfo, содержащего универсальные
методы вроде LastAccessTime и FullName.
И все же
самое принципиальное различие состоит в другом. Классы Directorylnfo и Filelnfo
гораздо лучше подходят для рекурсивного использования результатов, как было
показано в примере, приведенном в главе 4. Дело в том, что члены классов Directory
и File обычно возвращают строки с описанием каталогов или файлов, тогда как
члены классов Di rectorylnfo и Filelnfo обычно возвращают экземпляры своих классов.
Как было показано в главе 4, эта особенность упрощает написание рекурсивных
программ.
Между
этими парами существует еще одно тонкое различие: они обладают разными профилями
безопасности. Хотя в этой книге нам не удастся сколько-нибудь подробно описать
вопросы безопасности при программировании для .NET, вы должны хотя бы в общих
чертах понимать, что классы Directory и File проверяют привилегии вашего кода
для обращения или модификации файла или каталога при каждом использовании, а
классы Directorylnfo и Filelnfo проверяют их всего один раз при создании экземпляра
объекта. Именно этим и объясняется повышение их эффективности при многократном
выполнении операций с одним файлом или каталогом.
Поскольку
существование данных, к которым вы обращаетесь, не гарантировано, обращения
к файлам или каталогам часто заключаются в блоки Try-Catch. Впрочем, на эти
классы распространяется одно из основных правил при работе с исключениями: не
используйте исключения там, где можно ограничиться простой проверкой. Например,
в обычных условиях незачем перехватывать исключения Di rectoryNotFoundExcepti
on — проще предварительно вызвать метод Exists и убедиться в том, что каталог
существует. Ниже перечислены основные исключения, встречающиеся при операциях
с файлами и каталогами. Иерархию возглавляет базовый класс IOException:
IOException
>DirectoryNotFoundException
> EndOfStreamException
>FileLoadException
>FileNotFoundException
Прежде чем
рассматривать операции с каталогами и файлами, следует познакомиться с классом
Path. Этот класс содержит несколько общих методов, предназначенных для обработки
уточненных имен файлов [Любопытная подробность: в описании этого класса,
приведением в документации VB .NET, упоминаются некоторые аспекты кросс-платформенных
операций. В частности, упоминается о различиях между символом «/»
и разделителем каталогов «\», используемым в системах семейства
UNIX (в том числе и в системе BSD, для которой Microsoft анонсировала поддержку
CLR).]. Сетевые имена файлов устроены несколько сложнее локальных имен,
поэтому методы класса Path приносят несомненную пользу (кстати говоря, анализ
даже локальных имен — занятие на любителя). Основные члены класса Path перечислены
в табл. 9.1.
Таблица
9.1. Важнейшие члены класса Path
Член класса |
Описание |
||
DirectorySeparatorChar |
Символ-разделитель
каталогов для текущей платформы |
||
InvalidPathChars |
Массив всех
символов, недопустимых в уточненном имени файла |
||
PathSeparator |
Символ-разделитель
компонентов уточненного имени файла для текущей платформы |
||
VolumeSeparatorChar |
Символ-разделитель
имен томов для текущей платформы |
||
ChangeExtension(ByVal
path As String,ByVaL
extension As String) |
Изменяет расширение
файла и возвращает новое имя |
||
GetDirectoryName
(ByVal pathAs String) |
Возвращает путь
к каталогу, в котором находится файл |
||
GetExtension(ByVal
path As String) |
Возвращает расширение
файла |
||
GetFHeName(ByVal
path As String) |
Возвращает имя
и расширение для заданного уточненного имени |
||
GetFullPath(ByVat path As String) | Преобразует заданное имя файла в формат полного (fully qualified) имени | ||
GetPathRoot(ByVal path As String) | Возвращает корневой элемент заданного уточненного имени | ||
GetTempFileName (ByVal path As String) | Возвращает уникальное имя временного файла и создает на диске файл нулевой длины | ||
GetTempPath(ByVal path As String) | Возвращает путь к каталогу временных файлов в текущей системе | ||
GetFileNameWithoutExtension
(ByVal path As String) |
Возвращает имя
файла без расширения |
||
Большинство
методов класса Directory идентифицирует каталоги при помощи возвращаемых строк.
Поскольку все члены класса объявлены общими, при обращении к ним не обязательно
указывать конкретный экземпляр. Пример:
System.IO.Directory.GetCurrentDirectory()
Эта команда
возвращает строку с описанием текущего каталога. Метод GetDirectories(pathString)
возвращает массив строк с описанием подкаталогов каталога, заданного параметром
pathString. Описание интерпретируется либо как путь, заданный относительно каталога
текущего приложения, либо как путь в схеме UNC (Universal Naming Convention).
Следующая программа выводит имя текущего каталога и имена всех его подкаталогов.
Imports System.IO
Module Modulel
Sub Main()
Dim curDir.nextDir
As String Try
curDir =Directory.GetCurrentDirectory
()
Console.WriteLine(curDir)
For Each nextDir
In Directory.GetDirectories(curDir)
Console.WriteLine(nextDir)
Next
Catch ioe As
IOException
Console.WriteLine("eeeks
-i/o problems!" & ioe.message)
Catch e As
Exception
Console.Write(e.stacktrace)
Finally
Console.ReadLine()
End Try
End Sub
End Module
Если ваши потребности не ограничиваются простым выводом имен каталогов, лучше воспользоваться классом DirectoryInfo. Более подробное описание этого класса приводится ниже.
Помимо передачи
строки с описанием каталога методу GetDirectories можно передать шаблон с метасимволами,
используемыми в DOS [«?» обозначает один символ, а «*»
— несколько символов.]. Важнейшие методы класса Di rectory перечислены в
табл. 9.2. Во всех случаях параметры передаются по значению (с ключевым словом
ByVal).
Таблица
9.2. Важнейшие методы класса Directory
Метод |
Описание |
||
Create Directory
(ByVal pathName As String) |
Создает каталог
с заданным именем и возвращает объект Directory Info для созданного
каталога. При необходимости также создаются все промежуточные каталоги |
||
Delete(ByVal
pathName As String) |
Удаляет пустой
каталог. Чтобы удалить непустой каталог вместе со всеми каталогами
и файлами, воспользуйтесь командой Delete (pathName As String, True) |
||
Exists(ByVal
pathName As String) |
Возвращает логический
признак существования каталога |
||
GetCreationTime
(ByVal pathName As String) |
Возвращает объект
даты, содержащий информацию о дате и времени создания каталога |
||
GetCurrentDi
rectory |
Возвращает строку
с описанием текущего каталога |
||
GetDirectories
(ByVaL pathName As String) |
Возвращает массив
строк с описанием подкаталогов. При вызове может передаваться второй
строковый параметр, содержащий шаблон |
||
GetDi rectoryRoot
•(ByVal pathName As String) |
Возвращает строку
с описанием корневой части заданного пути |
||
GetFiles(ByVal
pathName As String) |
Возвращает массив
строк с описаниями файлов каталога. При вызове может передаваться
второй строковый параметр, содержащий шаблон |
||
GetLastAccessTime
(ByVal pathName As String) |
Возвращает объект
даты, содержащий информацию о времени последнего обращения к каталогу |
||
GetLastWriteTime
(ByVal pathName As String) |
Возвращает объект
даты, содержащий информацию о времени последней записи в каталог |
||
GetLogicalDrives |
Возвращает строковый
массив с именами логических дисков в формате «диск:\»
(например, С:\) |
||
GetParent (ByVal
pathName As String) |
Возвращает строку
с описанием каталога, родительского по отношению к заданному |
||
Move(ByVal sourceDirName
As String,ByVal destDirName As String) |
Перемещает каталог
со всем содержимым в пределах диска |
||
SetCurrentDirectory
(ByVal pathName As String) |
Задает текущий
каталог |
||
Класс File,
как и класс Directory, состоит из общих методов, которым при вызове обычно передается
имя файла. Эти методы применяктея при копировании, удалении
и перемещении файлов. Основные методы класса File перечислены в табл. 9.3. Обратите
внимание,— все параметры передаются по значению (в таблице отсутствуют методы
класса File, предназначенные для работы с потоками данных, — они будут рассмотрены
ниже).
Таблица
9.3. Основные методы класса File
Метод |
Описание |
||
Copy(ByVal sourceFiteName
As String,ByVal destFileName As String) |
Копирует файл.
Существует перегруженная версия метода с третьим логическим параметром
overwrite; если этот параметр равен True, существующий файл с заданным
именем перезаписывается |
||
Delete(ByVal
path As String) |
Удаляет заданный
файл. Интересная подробность: если файл не существует, исключение
не инициируется (см. описание метода Exists) |
||
Exists(ByVal
path As String) |
Возвращает логическую
величину, которая показывает, существует ли файл с заданным полным
именем |
||
GetAttributes(ByVal
path As String) |
Возвращает значение
перечисляемого типа FileAttributes с описанием атрибутов файла — архивный,
системный и т. д. (о том, как использовать полученный объект, рассказано
в следующем разделе) |
||
GetCreationTime
(ByVal path As String) |
Возвращает объект
даты, содержащий информацию о времени создания файла |
||
GetLastAccessTime
(ByVal path As String) |
Возвращает объект
даты, содержащий информацию о времени последнего обращения к файлу |
||
GetLastWriteTime
(ByVal path As String) |
Возвращает объект
даты, содержащий информацию о времени последней записи в файл |
||
Move(ByVal sourceFileName
As String, ByVal destFileName As String) |
Перемещает файл
(поддерживается возможность перемещения на другой диск) и переименовывает
его, если в параметре destFileName указано новое имя |
||
SetAttributes(ByVal
path As String, ByVal fileAttributes As FileAttributes) |
Задает атрибуты
указанного файла |
||
Операции
с атрибутами файлов и каталогов выполняются достаточно часто, поэтому в .NET
Framework был включен удобный класс FileAttri bute. Вероятно, правильнее было
бы назвать его FileDi rectoryAttri bute, поскольку все атрибуты относятся не
только к файлам, но и к каталогам.
Значения
перечисляемого типа обычно объединяются поразрядными операциями, чтобы избежать
всевозможных ошибок в программе. Не используйте команды следующего вида:
If File.GetAttributes("c:\foo.txt")
= FileAttributes.Readonly Then...
В проверяемом
условии не учитывается тот факт, что у файла могут быть установлены и другие
атрибуты. Правильная команда должна выглядеть так:
If File.GetAttributes("c:\foo.txt") And FileAttributes.Readonly _
= FileAttributes.Readonly
Then...
При необходимости
атрибуты объединяются оператором Оr. Пример:
File.SetAttributes(
"с: \foo.txt".
Not (FileAttributes.Archive)
Or FileAttributes.Hidden)
Команда назначает
атрибуты C:\foo.txt таким образом, что файл становится скрытым (Hidden), а архивный
бит (Archive) сбрасывается. Ниже перечислены важнейшие значения этого перечисляемого
типа:
Archive
Compressed
Di rectory
Encrypted
Hidden
Normal (атрибуты
не установлены)
Readonly
System
Классы
DirectoryInfo и FileInfo
В отличие
от обобщенных классов Directory и Filе классы Directory Info и FileInfо инкапсулируют
конкретные (или потенциально существующие) каталоги и файлы. Чтобы использовать
их, необходимо предварительно создать экземпляр класса. Под потенциальным существованием
мы имеем в виду, что объект Di rectorylnfo или Fi lelnfo может быть создан даже
в том случае, если файл или каталог с заданным именем еще не существует и создается
при последующем вызове метода Create.
Как правило,
при создании экземпляров этих классов при вызове конструктора указывается имя
каталога или файла. Пример:
Dim myDirectory
As Directorylnfo
myDirectory
= New Directorylnfo("C:\Test Directory")
Текущий каталог
обозначается символом «.»:
Dim currentDir
As New Directorylnfo(".")
После создания
объекта Directorylnfo можно запросить различные сведения о соответствующем каталоге
— например, время создания:
MsgBox(myDirectory.GreatienTime)
Как упоминалось
выше, одна из самых замечательных особенностей этих классов заключается в том,
что их члены возвращают объекты, а не строки. Например, в следующей программе
вызов GetFiles в выделенной строке возвращает коллекцию объектов Filelnfo, что
позволяет при необходимости вызвать методы этих объектов.
Imports System.IO
Module Modulel
Sub Main()
Dim myDi rectory
As Directorylnfo Try
myDirectory
=New DirectoryInfo("C:\Test Directory")
Dim aFile As
File Info
For Each aFile
In myDirectory.GetFiles
Consol e. WriteLi ne( "The fi1e named " & aFile. Full Name & _
"has length
" & aFile.Length) Next Catch e As Exception
MsgBox("eeks
-an exception " & e.StackTrace) Finally
Console.WriteLine("Press enter to end")
Console.ReadLine()
End Try
End Sub
End Module
Рекурсивный
просмотр дерева каталогов
Класс Directorylnfo
удобен тем, что на его основе легко строятся обобщенные процедуры для рекурсивного
перебора дерева каталогов. Как было показано в главе 4, при этом удобно использовать
вспомогательную процедуру, которая, в свою очередь, вызывает другую процедуру
для работы с файлами заданного каталога. Ниже приведена одна из возможных реализаций
этого рекурсивного процесса:
Option Strict
On Imports System.IO Module Modulel
SubMain()
Dim nameOfDirectory As String ="C:\"
Dim myDirectory As DirectoryInfo
myDirectory = New DirectoryInfo(nameOfDirectory)
WorkWithDirectory(myDirectory)
End Sub
Public Sub WorkWithDirectory(ByVal aDir As Directorylnfo)
Dim nextDir As Directorylnfo WorkWithFilesInDir(aDir)
For Each nextDir
In aDir.GetDirectories
WorkWithDirectory(nextDir) Next
End Sub
Public Sub WbrkWithFilesInDir(ByVal aDir As Directorylnfo)
Dim aFile As Filelnfo For Each aFile In aDir.GetFiles()
' Выполнить операцию с файлом.
' В нашем примере просто выводится уточненное имя.
Consolе.WriteLine(aFi1e.Ful1 Name) Next
End Sub
End Module
Следующий,
более реалистичный пример активизирует форму, показанную на рис. 9.1. Программа
заносит все скрытые файлы заданного каталога в список и продолжает рекурсивную
обработку дерева каталогов. Курсор мыши заменяется изображением песочных часов;
по этому признаку пользователь узнает о том, что программа выполняет какую-то
длительную операцию.
В
действительности эту программу следовало бы реализовать в многопоточной модели,
чтобы форма реагировала на действия пользователя, — о том, как это делается,
рассказано в следующей главе. Конечно, проблему можно решить включением команды
DoEvents в код обновления списка, однако многопоточное решение выглядит более
профессионально.
Private Sub Buttonl_Click(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles Buttonl.Click
'Заменить курсор изображением песочных часов
Me.Cursor = Cursors.WaitCursor
ListBoxl. Items. Clear()
WorkWithDirectory(New Directorylnfo(TextBoxl.Text))
Me.Cursor = Cursors.Default
End Sub
Public Sub WorkWithDirectory(ByVal aDir As Directorylnfo)
Dim nextDir As
Directorylnfo Try
WorkWithFilesInDir(aDir)
For Each nextDir
In aDir.GetDirectories
WorkWithDirectory(nextDi r) Next
Catch e As Exception
MsgBox(e.message SvbCrLf Se.StackTrace)
End Try
End Sub
Public Sub WorkWithFilesInDir(ByVal aDir As Directorylnfo)
Dim aFile As Filelnfo For Each aFile In aDir.GetFiles()
If aFile.Attributes
And _
FileAttributes.Hidden = FileAttributes.Hidden Then
ListBoxl. Items. Add( "FOUND hidden filenamed " & aFile. FullName)
End If
Next
End Sub
Рис.
9.1. Форма для рекурсивного просмотра каталогов
Помимо
мнврэпоточной реализации при более сложных операциях код процедуры WorkWithFHeslnDir
следовало бы заключить в блок Try-Catch.
Важнейшие
члены классов FileSystemInfo, FileInfo и DirectoryInfo
Класс FileSystemlnfo
является базовым для классов Directorylnfo и Filelnfo и содержит большую часть
их общей функциональности. Перед нами хороший пример тех возможностей, которые
открываются при использовании абстрактных базовых классов. В классе Directory
Info существует метод GetFileSystemlnfos, который возвращает массив объектов
FileSystemlnfо, представляющих файлы и подкаталоги заданного каталога. Такое
становится возможным только благодаря существованию класса FileSystemlnfo. Важнейшие
члены базового класса FileSystemlnf о перечислены в табл. 9.4.
Таблица
9.4. Члены базового класса FileSystemInfo
Метод/свойство |
Описание |
||
Attributes (свойство) |
Атрибуты объекта.
Свойство доступно для чтения и записи |
||
CreationTime
(свойство) |
Время создания
объекта. Свойство доступно для чтения и записи |
||
Exists (свойство) |
Логический признак
существования файла или каталога |
||
Extension (свойство) |
Расширение файла |
||
FullName (свойство) |
Полное имя каталога
или файла |
||
LastAccessTime
(свойство) |
Дата/время последнего
обращения к объекту. Свойство доступно для чтения и записи |
||
LastWriteTime
(свойство) |
Время последней
записи в объект. Свойство доступно для чтения и записи |
||
Name (свойство) |
Для файлов —
имя файла. Для каталогов — имя последнего каталога в иерархии, если
это возможно. В противном случае возвращается полное имя |
||
Delete |
Удаляет объект |
||
Refresh |
Обновляет состояние
объекта |
||
В табл. 9.5
и 9.6 перечислены важнейшие методы класса DirectoryInfo и методы класса Filelnfo,
не имеющие непосредственного отношения к потокам (эта тема будет рассматриваться
позже).
Таблица
9.5. Основные методы класса DirectoryInfo
Метод/свойство |
Описание |
||
Exists (свойство) | Логический признак существования каталога | ||
Name (свойство) | Имя каталога | ||
Parent (свойство) | Объект DirectoryInfo для родительского каталога (для корневых каталогов возвращается Nothing) | ||
Create | Создает каталог, путь к которому указан в конструкторе DirectoryInfo | ||
CreateSubdirectory (ByVal As String) | Создает подкаталог, путь к которому передается в виде параметра. Возвращает объект Directorylnfo для созданного подкаталога | ||
Delete | Удаляет пустой каталог, представленный объектом Directorylnfo. Если присвоить True необязательному логическому параметру, происходит рекурсивное удаление непустого каталога и всех его подкаталогов | ||
GetDirectories | Возвращает массив объектов Directorylnfo для подкаталогов текущего каталога | ||
GetFiles |
Возвращает массив объектов Filelnfo для файлов текущего каталога |
||
GetFileSystemlnfos | Хороший пример использования абстрактных классов: метод возвращает массив объектов FileSystemlnfo, представляющих все файлы и подкаталоги текущего каталога | ||
MoveTo(ByVal destDirName As String) | Перемещает Directorylnfo и все его содержимое | ||
Root (свойство) | Объект DirectoryIlnfo для корневого каталога в иерархии текущего каталога | ||
Таблица
9.6. Члены класса Filelnfo, не возвращающие потоков
Метод/свойство |
Описание |
||
Directory (свойство) | Объект Directorylnfo для каталога, в котором находится файл | ||
DirectoryName (свойство) | Полный путь к файлу в строковом виде | ||
Exists (свойство) | Логический признак существования файла | ||
Length (свойство) | Размер текущего файла | ||
CopyTo(ByVal destFileName As String) |
Копирует существующий файл и возвращает объект Filelnfo для копии. Необязательный логический параметр управляет перезаписью существующих файлов | ||
Create | Создает файл по имени, указанному при конструировании объекта Filelnfo, и возвращает объект FileSystem для нового файла | ||
Delete | Удаляет файл, представленный объектом FileInfo | ||
MoveTo(ByVal destFileName As String) | Перемещает файл | ||
Идея
выделения общей функциональности в абстрактный базовый класс выглядит впол-не
логично, однако в данном случае она реализована не лучшим образом. Например,
свдйство Length присутствует в файле FileInfo, но не поддерживается в FileSystemlnfo,
поэтому для вычисления размера дерева каталогов приходится прибегать к услугам
другого объекта — а именно вызывать метод Size объекта Folder, входящего в
модель FileSystemObject. Эта модель впервые была представлена в VBScript,
поэтому в решение приходится включать ссылку на библиотеку сценарной поддержки
на базе СОМ.
Как упоминалось
во вступительной части, одной из целей проектирования класса System. I0.Stream
было абстрагирование примитивных операций при работе с потоками
байтов. В соответствий с этой концепцией каждая конкретная реализация класса
Stream должна предоставить свои версии следующих методов:
Впрочем,
этим возможности не ограничиваются. Кроме простого перемещения от первого байта
к последнему реализация класса Stream может поддерживать и другие способы —
например, перемещение в обратном направлении или непосредственный переход к
заданной позиции в потоке. Такое возможно для файловых потоков, но не имеет
смысла (а следовательно, и не реализуется) для потоков, основанных на сетевых
соединениях. Свойство CanSeek позволяет узнать, поддерживает ли поток произвольный
доступ. Если свойство равно True, значит, в производном классе поддерживаются
реализации методов Seek и SetLength, а также свойств Position и Length.
В
реализации метода Seek обычно используются три значения (Begin, Current и End),
входящие в удобный перечисляемый тип SeekOrigin.
В табл. 9.7
перечислены основные методы абстрактного класса Stream, смысл которых должен
сохраниться и в производных классах.
Таблица
9.7. Основные методы класса Stream
Метод/свойство |
Описание |
||
CanRead (свойство) |
Логический признак
поддержки чтения |
||
CanSeek (свойство) |
Логический признак
поддержки произвольного доступа (позиционирования) |
||
CanWrite (свойство) |
Логический признак
поддержки записи |
||
Length (свойство) |
Длина потока
в байтах |
||
Position (свойство) |
Позиция в текущем
потоке (тип Long). Свойство доступно для чтения, а в некоторых потоках
— и для записи |
||
Close |
Закрывает поток
и освобождает используемые ресурсы (например, файловые манипуляторы
операционной системы) |
||
Flush |
Записывает данные
и стирает содержимое всех буферов, используемых потоком |
||
Read(ByVal buffer()
As Byte, ByVal offset As Integer, ByVal count As Integer) |
Читает заданное
количество байтов начиная с текущей позиции с прибавлением заданного
смещения offset. Возвращает количество успешно прочитанных байтов |
||
Read Byte |
Читает отдельный
байт (почему-то в формате Integer) в текущей позиции потока. Если
текущая позиция находится в конце потока, возвращает -1 |
||
Write(ByVal
buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) |
Записывает заданное
количество байтов начиная с текущей позиции с прибавлением заданного
смещения offset |
||
WriteByte(ByVal
value As Byte) |
Записывает байт
в текущую позицию потока |
||
Все классы
иерархии Stream поддерживают метод Close, освобождающий удерживаемые ресурсы
операционной системы (например, файловые манипуляторы или сетевые соединения),
поэтому практически во всех программах, работающих с потоками, рекомендуется
закрывать поток в блоке Try-Catch-Final 1у. Учтите, что вызов Close в секции
Finally требует предварительной проверки, поскольку этот метод вызывается лишь
для существующих объектов потоков, созданных успешным вызовом конструктора.
Проверка перед вызовом Close в секции Final ly выглядит примерно так:
Finally
If Not (myFileStream Is Nothing) Then myFileStream.Close()
End Try
Рассмотрите
и такую возможность, как реализация IDisposable в классах, выполняющих операции
с файлами, и закрытие всех открытых потоков методом Dispose.
Основные классы, производные от Stream, перечислены в табл. 9.8.
Таблица
9.8. Основные классы, производные от Stream
Класс |
Описание |
||
FileStream | Произвольный доступ к файлам | ||
MemoryStream | Представляет блок памяти (часто используется при работе с буферами) | ||
NetworkStream | Данные, полученные в виде потока по сетевому соединению. Принадлежит пространству имен System. Net. Sockets | ||
CryptoStream | Шифровка и расшифровка данных. Принадлежит пространству имен System. Security. Cryptography | ||
BufferedStream | «Оболочка» для поддержки буферизации в потоках, не обладающих этой возможностью (при использовании позволяет задать размер буфера). Например, автоматическая буферизация ввода используется в файловых потоках, но отсутствует в сетевых потоках. Если потребуется организовать буферизацию для сетевого потока, воспользуйтесь классом BufferedStream и методикой, описанной далее в этой главе | ||
В
.NET Framework входят классы для работы с XML, спроектированные по образцу
класса Stream. Впрочем, пространства имен XML в .NET велики и сложны, и о
них вполне можно было бы написать отдельную книгу.
Начнем с
рассмотрения команды, часто встречающейся при работе с файловыми потоками:
Dim myFileStream
As New FileStream("MyFile.txt". FileMode.OpenOrCreate, FileAccess.Write)
Как видно
из приведенного фрагмента, эта версия конструктора FileStream получает имя файла
(заданное по отношению к текущему каталогу, если не указано полное имя) и два
параметра, значения которых относятся к перечисляемым типам FileMode и FileAccess
соответственно. Таким образом, в нашем примере конструктор
Fi1eStream либо создает файл с именем MyFile.txt в текущем каталоге, либо открывает
его, если файл с таким именем уже существует. В любом случае программа
сможет записывать данные в файл. Часто встречаются и другие конструкторы класса
Fi leStream:
Допустимыми
значениями перечисляемого типа FileAccesS являются Read, Write и ReadWri te.
Основные значения перечисляемого типа Fi I eMode перечислены в табл. 9.9. Учтите,
что некоторые из них требуют особых привилегий для операций с файлами.
Таблица
9.9. Значения перечисляемого типа FileMode
Значение |
Описание |
||
Append | Открыть существующий файл (или создать несуществующий). Указатель текущей позиции перемещается в конец файла для записи. Используется совместно с FileAccess.Write | ||
Create | Создать новый файл. Внимание — существующий файл автоматически стирается! | ||
CreateNew | Создать новый файл. Отличается от Create тем, что для существующего файла инициируется исключение IOException | ||
Open | Открыть существующий файл. Если файл не существует, инициируется исключение IOException. Используется совместно с FileIOPermissionAccess.Read | ||
OpenOrCreate | Открыть или создать файл | ||
Truncate | Открыть существующий файл, удалить текущее содержимое | ||
Объекты
FHeStream также возвращаются следующими методами классов File и FHelnfo: File.Create,
File.Open, File.OpenRead, File.OpenWrite, FHeInfo.Create, FHelnfo.Open, FHelnfo.OpenRead.
Хотя файловые
потоки поддерживают произвольный доступ методом Seek, базовый класс-FileStream
ориентирован исключительно на операции с байтами, поэтому его возможности ограничиваются
простой записью байта или массива байтов методами WriteByte и Write. Приведенный
ниже фрагмент создает файл, показанный на рис. 9.2:
Option Strict On Imports System.IO
Module Modulel
Sub Main()
Dim i As Integer
Dim theBytes(255)
As Byte
For i = 0 To
255
theBytes(i)
= CByte(i)
Next
Dim myFileStream
As FileStream
Try
myFileStream
= New FileStream("C:\foo",
Fi1eMode.OpenOrCreate. FileAccess.Write)
myFlleStream.Write(theBytes,
0. 256) Finally
If Not (myFileStream Is Nothing) Then
myFileStream.Close()
End Try
DisplayAFile("C:\foo")
End Sub
End Module
Рис.
9.2. Запись двоичных данных в файл
После выполнения
этого фрагмента записанные данные можно прочитать методом Read, а также воспользоваться
методом Seek для перехода к произвольной позиции в файле. Впрочем, как это всегда
бывает при работе с неструктурированными потоками байтов, вам придется самостоятельно
преобразовать двоичные данные в более полезный формат. В результате сейчас трудно
найти более содержательный пример, чем простой вывод записанных чисел процедурой,
приведенной ниже:
Sub ReadDataBack()
Dim myFileStream
As Stream.i As Integer Try
myFileStream
= New FileStream("C:\foo",
FileMode.Open. FileAccess.Read)
For i = 0 To
255
Console.Write(myFileStream.ReadByte)
Next
Catch e As Exception
MsgBox(e.Message)
Finally
If Not (myFileStream Is Nothing) Then
myFileStream.Close()
End Try
End Sub
Метод Length
базового класса Stream всегда позволяет прочитать нужное количество байтов в
цикле независимо от структуры файла. Например, следующая процедура читает файл
по одному байту. Обнаруженные исключения просто передаются вызывающей стороне;
вероятно, в реальной программе следовало бы определить новый класс исключения:
Sub DisplayAFile(ByVal theFileName As String)
Dim theFile As FileStream
Dim i As Long
Try
theFile = New
FileStream(theFileName.
Fi1eMode.Open,Fi1eAccess.Read)
For i = 0 To (theFile.Length - 1)
' Вычесть 1. поскольку отсчет начинается с 0
Consolе.Write(theFiIe.ReadByte) Next
Catch Throw
Finally
If Not (theFile Is Nothing) Then theFile.Close()
End Try
End Sub
Если
файл имеет небольшие размеры и легко помещается в памяти, воспользуйтесь одним
вызовом Read и прочитайте весь файл в байтовый массив нужного размера. Такая
операция выполняется значительно быстрее.
Другой распространенный
вариант посимвольного чтения основан на том, что метод ReadByte в конце потока
возвращает -1. Основной цикл выглядит примерно так:
Dim i As Integer i = theFile.ReadByte
Do Until i =-1
Console.Write(i)
i = theFile.ReadByte
Loop
Чтение/запись
файла на уровне отдельных байтов используется не так уж часто; в основном это
необходимо при выполнении низкоуровневых операций. При операциях более высокого
уровня часто используется стандартный прием — неструктурированный файловый поток
передается конструктору потока, обладающего более широкими возможностями. Этот
принцип называется многоуровневой организацией потоков. Например, неструктурированный
файловый поток можно передать потоку, автоматически распознающему текст. Разные
способы многоуровневой организации потоков описаны в нескольких ближайших разделах.
Но прежде, чем переходить к этим разделам, просмотрите табл. 9.10 — в ней перечислены
основные методы и свойства базового класса FileStream. В дальнейшем мы будем
использовать эти
методы, хотя базовый файловый поток будет скрыт потоками более высоких уровней.
Таблица
9.10. Основные члены класса RleStream
Метод/свойство |
Описание |
||
Handle (свойство) |
Файловый манипулятор
операционной системы для файла, инкапсулированного в объекте FileStream.
Свойство доступно для чтения |
||
Length (свойство) |
Размер потока
в байтах. Свойство доступно для чтения |
||
Name (свойство) |
Уточненное имя,
переданное конструктору FileStream |
||
Position (свойство) |
Текущая позиция
для операций чтения или записи в потоке (нумерация позиций начинается
с нуля). Свойство доступно для чтения и записи |
||
Close |
Закрывает поток
и освобождает все связанные с ним ресурсы |
||
Flush |
Пересылает все
данные из буфера в устройство. Автоматически вызывается при вызове
Close |
||
Lock(ByVal position
As Long, ByVal length
As Long) |
Блокирует доступ
ко всему файлу или его части со стороны других процессов (нумерация
позиций начинается с нуля) |
||
Read(ByVal array()
As Byte, ByVal offset
As Integer, ByVal count
As Integer) |
Читает заданное
количество байтов в массив из файлового потока начиная
с заданной позиции |
||
ReadByte |
Читает один
байт из файла и перемещает указатель текущей позиции на один
байт вперед |
||
Seek(ByVal offset
As Long, ByVal origin
As SeekOrigin) |
Устанавливает
указатель текущей позиции в потоке в заданное положение |
||
Unlock(ByVal
position As Long, ByVal length
As Long) |
Снимает блокировку
с ранее заблокированной части файла (нумерация позиций
начинается с нуля) |
||
Write(ByVal
array() As Byte, ByVal offset
As Integer, ByVal count
As Integer) |
Записывает заданное
количество байт из массива в файловый поток начиная
с заданной позиции |
||
WriteByte |
Записывает байт
в текущую позицию файлового потока |
||
Чтение
и запись двоичных данных: классы BinaryReader и BinaryWriter
Операции
чтения и записи на уровне отдельных байтов слишком примитивны, и пользоваться
ими неудобно. По этой причине в .NET Framework предусмотрены гораздо более практичные
способы чтения и записи данных в файловые потоки. В этом разделе мы покажем,
как использовать классы BinaryReader и BinaryWriter для чтения и записи строк
и примитивных типов данных. Эти классы автоматически преобразуют примитивные
типы в двоичный формат, подходящий для сохранения на диске или пересылки по
сети. X
Объекты Bi
naryReader и BinaryWriter создаются посредством многоуровневого объединения
конструкторов потоков. Иначе говоря, конструктору класса потока более
высокого уровня вместо строки передается существующий объект потока. Пример
приведен ниже (в строке выделенной жирным шрифтом):
Dim aFileStream
As FileStream Try
aFileStream = New FileStream("c:\data.txt".FileMode.OpenOrCreate._
FileAccess.Write)
Dim myBinaryWriter
As New BinaryWriter(aFileStream)
myBinaryWriter.Write("Hello
world")
myBinaryWriter.writed)
Catch e as Exception
Console.Writeline(e.stacktrace)
Finally
If not(aFileStream is Nothing) Then aFileStream.Close()
End Try
Конструктору
класса Bi naryWriter передается объект файлового потока aFileStream. Полученный
в результате поток обладает расширенными возможностями и поддерживает запись
текстовых и числовых данных в файл в двоичном формате. Пример записи с использованием
класса BinaryWriter:
myBinaryWriter.Write("Hello
world") myBinaryWriter.wri ted)
Работа этого
фрагмента основана на перегрузке метода Write в классе Bi naryWriter, позволяющей
легко записывать в поток любые базовые типы данных. Ниже перечислены основные
перегруженные версии:
Sub Write(Byte)
Sub Write(Byte())
Sub Write(Char)
Sub Write(Char())
Sub Write(Decifnal)
Sub Write(Double)
Sub Write(Short)
Sub Write(Integer)
Sub Write(Long)
Sub Write(Byte)
Sub Write(Single)
Sub Write(String)
На рис. 9.3
показано, как созданный файл выглядит в шестнадцатеричном редакторе. Как видно
из рисунка, строка записана в виде кодов отдельных символов, но число кодируется
четырьмя байтами.
К сожалению,
хотя для записи в поток существуют различные перегруженные версии метода Write,
при чтении записанной информации средствами класса BinaryReader не существует
аналогичных перегруженных методов Read. Вместо этого для каждого типа данных
определяется собственная версия Read — ReadString, Readlnt32 (для типа Integer),
ReadChar и т. д. Вы должны знать, что и в каком порядке было записано в файл;
в противном случае восстановить исходные данные не удастся. Следующий фрагмент
показывает, как выполняется чтение в приведенном выше примере:
aFileStream = New FileStream("с:\data.txt", FileMode.Open. FileAccess.Read)
Dim myBinaryReader As New BinaryReader(aFileStream) Console._
WriteLine( myBinaryReader.ReadString)
Console.WriteLine(myBinaryReader.Readlnt32)
Рис.
9.3. Файл, записанный с применением класса BinaryWriter, в шестнадцатеричном
представлении
Если вы хотите
организовать обобщенное чтение двоичных данных и вас не интересует, какому типу
соответствуют прочитанные байты, воспользуйтесь методом PeekChar. Этот метод
проверяет, равен ли следующий байт -1 (признак конца файла в .NET). Цикл выглядит
примерно так:
While myBInaryReader.PeekChar()
<> -1
' Прочитать
следующий байт
Loop
Поскольку
чтение из файловых потоков буферизуется автоматически, в данном примере нет
необходимости добавлять новый уровень, передавая объект потока конструктору
BufferedStream.
TextReader,
TextWriter и производные классы
Двоичные
потоки чтения/записи хорошо подходят для случаев, когда программисту точно известен
порядок следования данных в двоичном формате, но прочитать полученный файл бывает
непросто. Таким образом, для хранения обычного текста в файле лучше поискать
другой вариант. В этой стандартной ситуации вместо пары BinaryReader/BinaryWriter
следует использовать пару StreamReader/StreamWriter. По функциональным возможностям
классы StreamReader и StreamWriter близки к традиционным средствам последовательного
доступа к файлам из прежних версий VB (если не считать того, что в этих классах
появилась поддержка Unicode). В классе StreamReader помимо метода Read также
имеется удобный метод ReadToEnd, позволяющий прочитать весь файл за одну операцию.
Обратите
внимание, что эти классы объявлены производными от абстрактных классов TextReader
и TextWriter, а не от Stream. Эти абстрактные классы, объявленные с атрибутом
Must Inherit, содержат общие средства чтения/записи текста. Их методы перечислены
в табл. 9.11 и 9.12.
Таблица
9.11. Основные методы класса TextReader
Метод |
Описание |
||
Close | Закрывает существующий поток TextReader и освобождает все системные ресурсы, связанные с ним | ||
Peek |
Возвращает следующий символ в потоке без смещения указателя текущей позиции |
||
Read | Читает один символ из входного потока. Перегруженная версия читает в символьный массив определенное количество символов начиная с заданной позиции | ||
ReadLine | Читает символы до комбинации CR+LF и возвращает их в виде строкового значения. Если текущая позиция находится в конце файла, метод возвращает Nothing | ||
ReadToEnd | Читает все символы от текущей позиции до конца TextReader и возвращает их в виде одной строки (метод особенно удобен при работе с небольшими файлами) | ||
Таблица
9.12.
Основные методы класса TextWriter
Метод |
Описание |
||
Close | Закрывает существующий поток TextWriter и освобождает все системные ресурсы, связанные с ним | ||
Write | Перегруженные версии метода позволяют записывать в поток любые базовые типы данных в текстовом формате | ||
WriteLine | Перегружается для записи в поток любых базовых типов данных в текстовом формате, за которыми записывается комбинация CR+LF | ||
Свойства
Console.In и Console.Out используемые при консольном вводе-выводе, в дей-ствительности
являются экземплярами классов TextReader и TextWriter. Методы Соп-sole.Setln
и Console.SetOut позволяют перенаправить стандартный ввод и вывод любым классам
*Reader и 'Writer соответственно.
Поскольку
классы TextReader и TextWriter являются абстрактными, программы работают с конкретными
реализациями StreamReader и StreamWriter. Как и в случае с классами BinaryReader
и BinaryWriter, при создании объектов StreamReader и StreamWriter конструктору
обычно передается существующий объект потока:
myFile = New FileStreamtfileName.FileMode.Open, FileAccess.Read)
textFile= New
StreamReader(myFile)
Для получения
объекта также можно воспользоваться методами класса File. Пример неявного создания
объекта StreamReader при создании файлового потока продемонстрирован ниже:
Dim aStreamReader As StreamReader
aStreamReader
= File.OpenText ("sample.txt")
Объекты класса
StreamWriter создаются аналогичным образом:
Dim aStreamWriter As StreamWriter
aStreamWriter
= File.CreateText ("test.txt")
Данные записываются
в поток методами Write и WriteLine. Что касается чтения, в вашем распоряжении
два способа. В наиболее распространенном варианте программа в цикле читает строки
до тех пор, пока очередная прочитанная строка не окажется равной Nothing. В
программе это выглядит примерно так:
Dim s As String
Do
s = theStreamReader.ReadLine
If Not s Is Nothing Then
' Выполнить нужные действия с s.
' Например, вызвать Console.WriteLine(s).
End If
Loop Untils Is
Nothing
Также можно
воспользоваться методом Peek и проверить, равен ли следующий читаемый символ
-1 (признак конца файла):
Do Until theStreamReader.Peek
= -1
В качестве
примера использования класса TextReader ниже приводится простая процедура, предназначенная
для вывода текстового файла на экран. Обратите внимание: в строках 5-17 весь
важный код заключен в блок Try-Catch-Finally. В этом блоке программа пытается
закрыть открытый поток независимо от того, что произошло при операциях с ним.
Как упоминалось выше, перед вызовом Cl ose в строке 16 сначала необходимо убедиться
в том, что поток был успешно создан. Также обратите внимание на то, как в строке
14 к инициируемому исключению добавляется содержательное сообщение. В реальной
программе следовало бы определить новый класс исключения (за подробностями обращайтесь
к главе 7).
1 Sub DisplayTextFile(ByVal
fName As String)
2 Dim myFile
As FileStream
3 Dim textFile
As StreamReader
4 Dim stuff
As String
5 Try
6 myFile = New
FileStream(fName.FileMode.Open, FileAccess.Read)
7 textFile =
New StreamReader(myFile)
8 stuff = textFile.ReadLine()
9 Do Until stuff
Is Nothing
10 Console.WriteLine(stuff)
11 stuff = textFile.ReadLine()
12 Loop
13 Catch e As
Exception
14 Throw New
Exception("If the file existed.it was closed")
15 Finally
16 If Not (myFile
Is Nothing)Then myFile.Close()
17 End Try
18 End Sub
19 End Module
В общем случае
отдельные строки файла можно сохранить в динамическом массиве ArrayList (если,
конечно, количество строк относительно невелико). Для этого достаточно внести
минимальные изменения в предыдущую программу. В заголовок процедуры добавляется
новый параметр:
Sub DisplayTextFile(ByVal fName As String,ByVal where As ArrayList)
Строка
10 приводится к следующему виду:
where, Add(stuff)
Объектные
потоки: сохранение и восстановление объектов
Объектно-ориентированное
программирование вряд ли получило бы столь широкое признание, если бы программист
не мог сохранить объект в текущем состоянии и восстановить его позднее. Запись
объекта в поток данных называется сериализацией (serialization), а обратный
процесс называется десериализацией (deserialization). В нескольких ближайших
разделах мы познакомим читателя с основными принципами сериализации и десериализации.
Но прежде,
чем переходить к рассмотрению новой темы, следует заметить, что это более сложная
и тонкая проблема, чем кажется на первый взгляд. Почему? Одна из причин заключается
в том, что объект может содержать другие объекты (вспомните классы Manager и
Secretary из главы 5). Следовательно, процесс сохранения должен поддерживать
рекурсивное сохранение внутренних объектов. Более того, при этом необходимо
позаботиться об отсутствии дублирования. Если на 100 программистов в отделе
приходится одна секретарша, было бы нежелательно сохранять данные секретарши
в 100 экземплярах, когда вполне достаточно одного экземпляра с соответствующей
настройкой ссылок (нечто похожее происходит при приведении баз данных к нормальной
форме с исключением избыточных данных).
К счастью,
в .NET Framework сохранение объектов не требует особых усилий со стороны программиста.
Как будет вскоре показано, объекты можно сохранять даже в понятном для человека
формате SOAP (Simple Object Access Protocol), основанном на языке XML.
Прежде всего
импортируйте пространство имен System.Runtime.Serialization, это сэкономит немало
времени на вводе имен. В типичной ситуации поддержка сериализации включается
простым добавлением атрибута в заголовок класса:
<Serializable()>Public
Class Employee
Атрибут <Serial
izable( )> должен быть установлен и во всех классах, производных от вашего
класса, а также во всех вложенных классах, объекты которых содержатся в исходном
классе. В противном случае рекурсивный процесс будет нарушен с исключением System.Runtime.Serialization.Serial
izationException.
В
.NET Framework сериализация поддерживается в классах, реализующих интерфейс
ISerializable.
После пометки
класса атрибутом <Serial izableC )> следует решить, в каком формате должен
сохраняться объект — в XML-формате SOAP или в более компактном двоичном формате.
Используемый по умолчанию двоичный формат доступен всегда. Чтобы воспользоваться
форматом SOAP, необходимо добавить ссылку на сборку System. Runti me. Sena1izati
on. Formatters. Soap.
Следующий
пример показывает, как организовать сериализацию для массива. Массив ArrayList
является объектом и может содержать другие объекты (в нашем примере это объекты
иерархии Employee). Поскольку динамические массивы сери-ализуются автоматически,
остается лишь пометить атрибутом <Serializabl е( )> различные классы иерархии
Empl oyee. Вся содержательная работа выполняется в двух выделенных строках:
Sub SerializeToBinary(ByVal myEmployees As ArrayList._
ByVal fName As String)
Dim fStream As
FileStream
Dim myBinaryFormatter As New Formatters.Binary.BinaryFormatter()
Try
fStream = New
FileStreamtfName,FileMode.Create, FileAccess.Write)
myBinaryFormatter.Serialize(fStream, myEmployees)
Catch e As Exception
Throw e Finally
If Not (fStream Is Nothing) Then fStream.Close()
End Try
End Sub
Чтобы вместо
двоичного формата массив сохранялся в формате SOAP, достаточно включить в проект
ссылку на сборку System.Runtime.Serialization.Formatters.Soap (это делается
в диалоговом окне Project > References) и привести выделенные строки
к следующему виду:
Dim mySoapFormatter
As New Formatters.Soap.SoapFormatter()
и
mySoapFormatter.Serialize(fStream.
myEmployees)
На рис. 9.4
показано, как выглядит полученный файл в формате SOAP.
Отдельные
поля класса можно пометить атрибутом <NonSerialized()>. Состояние этих
полей не сохраняется в процессе сериализации.
С восстановлением
сохраненного объекта дело обстоит сложнее: поскольку при десериализации возвращается
тип Object, приходится выполнять явное преобразование к нужному типу, как в
выделенной строке следующего фрагмента:
Function DeSerializeFromSoap(ByVal
fName As String) As ArrayList
Dim fStream
As New FileStreamtfName.FileMode.Open. FileAccess.Read)
Dim mySoapFormatter
As New Formatters.Soap.SoapFormatter()
Try
fStream = New
FileStream("C:\test.xml". FileMode.Open.
FileAccess.Read)
Return CType(mySoapFormatter.Deserialize(fStream), ArrayList)
Catch e As Exception
Throw e Finally
If Not (fStream Is Nothing) Then fStream.Close()
End Try
End Function
Рис.
9.4. Объект, сохраненный в формате SOAP
Применение
сериализации при клонировании объектов
У сериализации
имеется и такое нетривиальное применение, как клонирование сложных объектов.
Фокус заключается в том, чтобы записать объект в поток памяти MemoryStream и
затем восстановить его (потоки MemoryStream позволяют работать с данными в быстрой
оперативной памяти по аналогии с тем, как поток FileStream работает с файлом
на диске). Ниже приведен типичный код клониро-вания:
Public Function
Clone()As Object Implements ICloneable.Clone
Dim myBinaryFormatter As New Formatters.Binary.BinaryFormatter()
Try
Seriali'zeToBinary()
mSTream.Position = 0
Return myBinaryFormatter.Deserialize(mSTream)
Finally
mSTream.Close()
End Try
End Function
Sub SerializeToBinary()
Dim myBinaryFormatter As New Formatters.Binary.BinaryFormatter()
Try
mSTream = New MemoryStream()
myBinaryFormatter.Serialize(mSTream.Me)
Catch
Throw
End Try
End Sub
Практический
пример: динамический список с поддержкой сериализации
Прежде чем
приводить полный код примера, мы хотим предупредить об одной потенциальной трудности,
которая постоянно возникает при восстановлении сохраненных объектов, а особенно
объектов, хранящихся в динамических списках. Итак, после завершения восстановления
мы получаем набор обобщенных объектов, хранящихся в динамическом массиве.
Но как определить истинный тип этих объектов, чтобы выполнить правильное преобразование?
В следующем примере эта информация жестко фиксируется в процессе восстановления,
поскольку мы точно знаем порядок занесения объектов иерархии Employee в массив.
В более общей ситуации эти сведения пришлось бы сохранять в отдельном файле.
В настоящем
примере мы создаем менеджера (класс Manager) с именем Sally и секретаря (класс
Secretary) с именем Тот. Класс Manager содержит внутренний объект класса Secretary
в одной из переменных; класс Secretary содержит ссылку на Manager.
Не
забудьте включить в решение ссылку на сборку System.Runtime.Serialization.For-matters.Soap,
это необходимо для работы программы.
Ниже приведен
код тестовой части программы. Три ключевые строки выделены жирным шрифтом:
Option Strict
On
' Использует
сборку System.Runtime.Serialization.Formatters.Soap
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters
Module Modulel
Sub Main()
Dim Sally As
New Manager("Sally". 150000)
Dim Tom As Secretary
Tom = New Secretary("Tom".
100000, Sally)
Sally.MySecretary
= Tom
Dim Employees As New ArrayList() Employees. Add(Tom)
Employees.Add(Sally)
Console.WriteLine(Tom.TheName
& "is employee " & _
Tom.ThelD & "and has salary " & Tom.Salary)
Console.WriteLine("Tom's boss is " & Tom.MyManager.TheName)
Console.WriteLine("Sally's secretary is " & Sally.MySecretary.TheName)
Console. WriteLine()
Console.Writel_ine(Sally.TheName & "is employee " & _
Sally.ThelD & "has salary " & Sally.Salary) Sally.RaiseSalary(0.lD)
Console.WriteLinet"After
raise " & Sally.TheName &_ "has salary "_
& Sally.Salary)
Рис.9.5. Сериализация динамического массива
Ниже приведена
остальная часть кода этого примера.
Sub SerializeToSoap(ByVal myEmployees As ArrayList._
ByVal fName As String)
Dim fStream
As FileStream
Dim mySoapFormatter As New Formatters.Soap.SoapFormatter()
Try
fStream = New FileStreamtfName. FileMode.Create.FileAccess.Write)
mySoapFormatter.Serialize(fStream. myEmployees)
Catch
Throw Finally
If Not (fStream Is Nothing) Then fStream.Close()
End Try
End Sub
Function DeSerializeFromSoap(ByVal
fName As String) As ArrayList
Dim fStream
As New FileStream(fName. Fi1eMode.Open. FileAccess.Read)
Dim mySoapFormatter
As New Formatters.Soap.SoapFormatter()
Try
fStream = New FileStream(fName, FileMode.Open. FileAccess.Read)
Return
CType(mySoapFormatter.Deserialize(fStream), ArrayList)
Catch
Throw Finally
If Not (fStream Is Nothing) Then fStream.Close()
End Try
End Function
End Module
<Serializable()>Public
Class Employee
Private m_Name
As String
Private m_Salary
As Decimal
Private Const
LIMIT As Decimal = 0.1D
Private Shared
m_EmployeeId As Integer = 1000
Private m_myID
As Integer
Public Sub New(ByVal
sName As String. ByVal curSalary As Decimal)
m_Name = sName
m_Salary = curSalary
m_myID = m_EmployeeId
m_EmployeeId = m_EmployeeId + 1
End Sub
Readonly Property
TheIDO As Integer
Get
Return mjnyID
End Get
End Property
Readonly Property TheName()As String
Get
Return m_Name
End Get
End Property
Readonly Property Salary()As Decimal
Get
Return MyClass.m_Salary
End Get
End Property Public Overridable Overloads
Sub
RaiseSalary(ByVal Percent As Decimal)
If Percent > LIMIT Then
' Недопустимая операция
Console.WriteLineC'MUST
HAVE PASSWORD " & _
"TO RAISE
SALARY MORE THAN LIMIT!!!!") Else
m_Salary = (1 + Percent) * m_Salary
End If
End Sub
Public Overridable Overloads
Sub RaiseSa1ary(ByVal Percent As Decimal._
ByVal Password
As String) If Password = "special" Then
m_Salary = (1 + Percent) * m_Salary
End If
End Sub
End Class
<Serializable()>Public
Class Manager
Inherits Employee
Private m_Sec
As Secretary
Private m_Salary
As Decimal
Public Sub New(ByVal sName As String,_
ByVal curSalary
As Decimal)
MyBase.New(sName. curSalary)
End Sub
Public Sub New(ByVal
sName As String.ByVal curSalary As Decimal.
ByVal mySec As Secretary)
MyBase.New(sName.curSalary)
m_Sec = mySec
End Sub
Property MySecretary()As
Secretary Get
Return m_Sec
End Get Set(ByVal Value As Secretary)
m_Sec = Value
End Set
End Property
Public Overloads Overrides
Sub RaiseSalary(ByVal
percent As Decimal)
MyBase.RaiseSalary(2 * percent, "special")
End Sub
End Class
<Serializable()>
Public Class
Secretary Inherits Employee
Private m_Boss
As Manager
Public Sub New(ByVal
sName As String. ByVal curSalary As Decimal,
ByVal myBoss As Manager) MyBase.New(sName, curSalary)
m_Boss = myBoss
End Sub
Property MyManager()
As Manager Get
Return m_Boss
End Get Set(ByVal
Value As Manager)
m_Boss = Value
End Set
End Property
End Class
Среди областей,
в которых особенно наглядно проявляются возможности абстрактной модели потока,
особое место занимает пересылка информации в Интернете. Работа с низкоуровневым
кодом HTML и XML почти не требует усилий со стороны программиста. Хотя в этом
разделе мы сможем дать лишь общее представление об этой важной теме и о задействованных
пространствах имен, по крайней мере вы увидите, как потоковая интерпретация
сетевых данных реализуется на практике. В рассмотренном ниже примере мы передаем
информацию на web-сайт и получаем непосредственный HTML-код новой страницы в
качестве результата запроса. Анализ полученного HTML-кода приносит нужную информацию.
Мы не смогли
устоять перед искушением: наше маленькое приложение обращается на сайт Amazon.com
и возвращает текущие сведения о количестве проданных экземпляров нашей книги.
Обобщенный алгоритм выглядит следующим образом:
В данном
случае страница генерируется следующей строкой запроса, которая и будет использована
для создания объекта URI (в конце строки приведен номер ISBN нашей книги):
http://www.amazon.com/exec/obidos/ASIN/1893115992
Следующий
конструктор создает экземпляр класса с номером ISBN, переданным в виде строкового
параметра:
Public Sub New(ByVal
ISBN As String)
m_URL ="http://wvM.amazon.com/exec/obidos/ASIN/"
& ISBN
End Sub
Доступное
только для чтения свойство GetRank нашего класса просто вызывает закрытую функцию,
основной код которой приведен в следующих восьми строках:
1 Dim theURL
As New URI(m_URL)
2 Dim theRequest
As WebRequest
3 theRequest
= WebRequest.Create(theURL)
4 Dim theResponse
As WebResponse
5 theResponse
= theRequest.GetResponse
6 Dim aReader
As New StreamReader(theResponse.GetResponseStream())
7 Dim theData
As String .
8 theData =
aReader.ReadToEnd
В строке 1 создается объект класса URI. В строках 2 и 3 генерируется web-запрос, передаваемый на сайт Amazon.com. Строки 4 и 5 принимают ответ на запрос, а в строке 6 метод GetResponseStream класса Response конструирует объект StreamReader для полученного потока. На этой стадии строковая переменная theData содержит низкоуровневый HTML-код web-страницы нашей книги.
<font face=verdana.arial.helvetica
size=-l>
<b>Amazon.com
Sales Rank:</b>
5.776
</font><br>
Остается
лишь проанализировать переменную theData и извлечь из нее данные о продажах.
Для этого мы воспользуемся вспомогательной функцией Analyze:
Private Function Analyze(ByVal theData As String)As Integer
Dim Location
As Integer
Location - theData.IndexOf("<b>Amazon.com
Sales Rank:</b>")
+ "<b>Amazon.com
Sales Rank:</b>".Length
Dim temp As
String
Do Until theData.Substring(Location.l) = "<" temp = temp
StheData.Substring(Location.l)
Location +=
1
Loop
Return CInt(temp)
End Function
Для
анализа строковой переменной также можно воспользоваться классом регулярных
выражений из пространства имен System.Text.
Ниже приведен
полный код тестового модуля (разумеется, для тестирования вам также понадобится
Интернет-соединение):
Option Strict On Imports System.IO Imports System.Net
Module Module1
Sub Main()
Dim myBook As New AmazonRanker("1893115992")
MsgBox("This book's current rank is " & myBook.GetRank)
End Sub
End Module
Public Class
AmazonRanker
Private m_URL
As String
Private m_Rank
As Integer
Public Sub New(ByVal
ISBN As String)
m_URL = "http://www.amazon.com/exec/obidos/ASIN/" & ISBN
End Sub
Public Readonly
Property GetRank()
As Integer
Get Return ScrapeAmazon()
End Get End
Property
Private Function
ScrapeAmazon() As Integer Try
Dim theURL As
New URI(m_URL)
Dim theRequest
As WebRequest
theRequest =
WebRequest.Create(theURL)
Dim theResponse
As WebResponse
theResponse
= theRequest.GetResponse
DimaReaderAsNew
StreamReader(theResponse.GetResponseStream())
Dim theData
As String
theData = aReader.ReadToEnd
Return Analyze(theData)
Catch E As Exception
Console.WriteLine(E.StackTrace)
Console. ReadLine()
End Try
End Function
Private Function Analyze(ByVal theData As String) As Integer
Dim Location
As Integer
Location = theData.IndexOf("<b>Amazon.com Sales Rank:</b>") + "<b>Amazon.com
Sales Rank:</b>".Length
Dim temp As String
Do Until theData.Substring(Location.l) = "<" temp - temp
&theData.Substring(Location,l)
Location += 1 Loop
Return CInt(temp)
End Function
End Class
Пример
этой программы наглядно показывает, какие неуловимые проблемы порой возникают
в результате локализации. Когда наш друг запустил эту программу в Европе, она
отказалась работать. Оказалось, что на сайте Amazon по вполне понятным причинам
используется американский числовой формат, а программа запускалась в европейской
версии Windows, в результате чего символ «,» интерпретировался неверно.
Разумеется, проблема легко решается — достаточно, чтобы функция возвращала значение
строкового типа.
К числу принципиальных
новшеств, отличающих VB .NET от предыдущих версий VB, относится и возможность
сделать на сервере то, что в VB давно делалось для клиентов. Речь идет об инкапсуляции
общей функциональности в элементах и многократном использовании кода. В завершение
этой главы мы покажем, как использовать класс FileSystemMonitor для написания
программы, которая отслеживает изменения в заданном каталоге и сигнализирует
о них при помощи событий.
Программа
может следить за каталогом или набором файлов, соответствующих заданному фильтру.
Элемент Fil eSystemMoni tor даже может произвести рекурсивный
перебор всех подкаталогов заданного каталога. Инициируемые события перечислены
в табл. 9.13.
Таблица
9.13. События монитора файловой системы
Событие |
Описание |
||
Changed |
Изменения в
размере, системных атрибутах, времени последней записи, времени последнего
обращения или привилегиях безопасности для подкаталога или файла |
||
Created |
Создание подкаталога
или файла |
||
Deleted |
Удаление подкаталога
или файла |
||
Renamed |
Переименование
подкаталога или файла |
||
Впрочем,
компонент FileSystemMonitor не всесилен — в частности, он не позволяет отслеживать
изменения в самом каталоге. Если кто-то переименует файл, находящийся в каталоге,
вы об этом узнаете, однако переименование самого каталога останется незамеченным
(конечно, для отслеживания подобных изменений можно дополнительно следить за
родительским каталогом).
Компонент
Fi1eSystemMoni tor, как и все компоненты разных панелей элементов VS .NET, является
конкретной реализацией более общего класса. В данном случае это класс FileSystemWatcher,
производный от класса Component. Режим отслеживания подкаталогов включается
следующей командой:
FileSystemWatcherl.IncludeSubdirectohes
= True
На рис. 9.6
изображен примерный вид формы. Компонент FileSystemWatcher находится на вкладке
Components. Визуального интерфейса он не имеет и поэтому при размещении на форме
он отображается на служебной панели, показанной в нижней части рис. 9.6.
Монитор активизируется
следующей несложной процедурой:
Private Sub btnStart_Click(ByVal sender As System.Object,_
ByVal e As System.EventArgs)Handles btnStart.Click
If CheckPath()Then
FileSystemWatcherl.Path = txtDirectory.Text
FileSystemWatcherl.IncludeSubdirectories = chkRecursive.Checked
FileSystemWatcherl.EnableRaisingEvents = True
End If
End Sub
Для пущей
надежности мы убеждаемся в том, что заданный каталог существует. При проверке
используется класс Directory, поэтому программа должна импортировать пространство
имен System. 10:
Function CheckPath()As
Boolean
If Directory.Exists(txtDirectory.Text)
Then
Return (True)
Else
txtDirectory.Text=
"" txtDirectory.Focus 0
MsgBox("No
directory by that name exists!") Return False
End If End Function
Рис.
9.6. Пример использования компонента FileSystemWatcher
VB .NET автоматически
подключает обработчик события. В следующем фрагменте при изменениях в заданном
каталоге вызывается окно сообщения:
Private Sub
FileSystemWatcherl_Changed(ByVal sender As Object.
ByVal e As System.IO.FileSystemEventArgs)
Handles
FileSystemWatcher1.Changed
MsgBox(txtDirectory.Text & "has changed!")
End Sub
К сожалению,
мы не сможем полностью описать этот замечательный компонент. Но прежде, чем
вы перейдете к самостоятельным исследованиям, примите к сведению пару полезных
советов:
Если вам
потребуется более точный контроль, обратитесь к описанию свойства NotifyFilter
в электронной документации. Его значение задается в виде констант перечисляемого
типа, объединенных оператором Ог, и определяет типы отслежи-
ваемых изменениях.
Например, можно отслеживать изменения атрибутов, имени и размера файла.
Без
задания свойств Filter и NotifyFilter программа, осуществляющая рекурсивный
мониторинг активного или корневого каталога, становится практически бесполезной
— событие Changed будет слишком часто срабатывать в результате обычных служебных
операций Windows.