Глава 9. Ввод-вывод

Глава №9.

Ввод-вывод

В прежних версиях 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 и Directorylnfo.
  • Классы File и Filelnfo.

Обращение к функциональным возможностям классов 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

Прежде чем рассматривать операции с каталогами и файлами, следует познакомиться с классом 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

Большинство методов класса 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

Класс 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 должна предоставить свои версии следующих методов:

  1. Read — метод чтения данных из потока. Иногда сводится к простейшему чтению одного байта, но во многих производных классах используются сложные методы для чтения данных большими порциями.
  2. Write — метод записи данных в поток. Как и предыдущий метод, может сводиться к простейшей записи одного байта, но может задействовать и фрагменты данных большего размера.

Впрочем, этим возможности не ограничиваются. Кроме простого перемещения от первого байта к последнему реализация класса 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:

  • Sub New(String, FileMode): создает объект FileStream с заданным именем и в заданном режиме (см. ниже описание FileMode).
  • Sub NewCString, FileMode, FileAccess): создает объект FileStream в заданном режиме, с заданными правами чтения/записи и совместного доступа.

Допустимыми значениями перечисляемого типа 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 и возвращает текущие сведения о количестве проданных экземпляров нашей книги. Обобщенный алгоритм выглядит следующим образом:

  1. Создать объект URI (Universal Resource Locator) передачей строкового параметра конструктору класса URI.
  2. Передать объект URI методу Create класса HttpWebRequest, чтобы инициировать выдачу запроса HTTP.
  3. Вызвать метод GetResponse класса HttpWebRequest и получить поток.
  4. Проанализировать полученный поток, содержащий HTML-код, и извлечь из него нужную информацию, для чего необходимо знать структуру страницы. Кстати, это одна из причин, по которым для получения данных удобнее использовать web-службы: если Amazon неожиданно сменит структуру своих страниц, наше приложение перестанет работать.

В данном случае страница генерируется следующей строкой запроса, которая и будет использована для создания объекта 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

К сожалению, мы не сможем полностью описать этот замечательный компонент. Но прежде, чем вы перейдете к самостоятельным исследованиям, примите к сведению пару полезных советов:

  • Свойство Filter устанавливает фильтр для имен файлов и каталогов, за которыми вы хотите следить.
  • Существует много разных типов изменений, поэтому в реальных программах .рекомендуется более точно определять интересующие вас события. Например, если вы хотите, чтобы программа оповещалась только о создании новых файлов, организуйте обработку события Created.

Если вам потребуется более точный контроль, обратитесь к описанию свойства NotifyFilter в электронной документации. Его значение задается в виде констант перечисляемого типа, объединенных оператором Ог, и определяет типы отслежи-

ваемых изменениях. Например, можно отслеживать изменения атрибутов, имени и размера файла.

Без задания свойств Filter и NotifyFilter программа, осуществляющая рекурсивный мониторинг активного или корневого каталога, становится практически бесполезной — событие Changed будет слишком часто срабатывать в результате обычных служебных операций Windows.



Опубликовал admin
22 Авг, Воскресенье 2004г.



Программирование для чайников.