Cамоучитель по VB.NET

         

Атрибуты файла


Операции с атрибутами файлов и каталогов выполняются достаточно часто, поэтому в .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



Чтение и запись двоичных данных: классы 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)



Файл, записанный с применением




Двоичные потоки чтения/записи хорошо подходят для случаев, когда программисту точно известен порядок следования данных в двоичном формате, но прочитать полученный файл бывает непросто. Таким образом, для хранения обычного текста в файле лучше поискать другой вариант. В этой стандартной ситуации вместо пары BinaryReader/BinaryWriter следует использовать пару StreamReader/StreamWriter. По функциональным возможностям классы StreamReader и StreamWriter близки к традиционным средствам последовательного доступа к файлам из прежних версий VB (если не считать того, что в этих классах появилась поддержка Unicode). В классе StreamReader помимо метода Read также имеется удобный метод ReadToEnd, позволяющий прочитать весь файл за одну операцию.

Обратите внимание, что эти классы объявлены производными от абстрактных классов TextReader и TextWriter, а не от Stream. Эти абстрактные классы, объявленные с атрибутом Must Inherit, содержат общие средства чтения/записи текста. Их методы перечислены в табл. 9.11 и 9.12.



Каталоги и файлы


В 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



Класс 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

Consol e. 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)

Задает текущий каталог

Содержание
Вперед




Класс Path


Прежде чем рассматривать операции с каталогами и файлами, следует познакомиться с классом Path. Этот класс содержит несколько общих методов, предназначенных для обработки уточненных имен файлов [ Любопытная подробность: в описании этого класса, приведением в документации VB .NET, упоминаются некоторые аспекты кросс-платформенных операций. В частности, упоминается о различиях между символом «/» и разделителем каталогов «\», используемым в системах семейства UNIX (в том числе и в системе BSD, для которой Microsoft анонсировала поддержку CLR). ]. Сетевые имена файлов устроены несколько сложнее локальных имен, поэтому методы класса Path приносят несомненную пользу (кстати говоря, анализ даже локальных имен — занятие на любителя). Основные члены класса Path перечислены в табл. 9.1.



Классе File



Класс File, как и класс Directory, состоит из общих методов, которым при вызове обычно передается имя файла. Эти методы применяктея при копировании, удалении и перемещении файлов. Основные методы класса File перечислены в табл. 9.3. Обратите внимание,— все параметры передаются по значению (в таблице отсутствуют методы класса File, предназначенные для работы с потоками данных, — они будут рассмотрены ниже).



Классы 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



Монитор файловой системы


К числу принципиальных новшеств, отличающих VB .NET от предыдущих версий VB, относится и возможность сделать на сервере то, что в VB давно делалось для клиентов. Речь идет об инкапсуляции общей функциональности в элементах и многократном использовании кода. В завершение этой главы мы покажем, как использовать класс FileSystemMonitor для написания программы, которая отслеживает изменения в заданном каталоге и сигнализирует о них при помощи событий.

Программа может следить за каталогом или набором файлов, соответствующих заданному фильтру. Элемент Fil eSystemMoni tor даже может произвести рекурсивный перебор всех подкаталогов заданного каталога. Инициируемые события перечислены в табл. 9.13.



Объект, сохраненный в формате 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



Объектные потоки: сохранение и восстановление объектов


Объектно-ориентированное программирование вряд ли получило бы столь широкое признание, если бы программист не мог сохранить объект в текущем состоянии и восстановить его позднее. Запись объекта в поток данных называется сериализацией (serialization), а обратный процесс называется десериализацией (deserialization). В нескольких ближайших разделах мы познакомим читателя с основными принципами сериализации и десериализации.

Но прежде, чем переходить к рассмотрению новой темы, следует заметить, что это более сложная и тонкая проблема, чем кажется на первый взгляд. Почему? Одна из причин заключается в том, что объект может содержать другие объекты (вспомните классы Manager и Secretary из главы 5). Следовательно, процесс сохранения должен поддерживать рекурсивное сохранение внутренних объектов. Более того, при этом необходимо позаботиться об отсутствии дублирования. Если на 100 программистов в отделе приходится одна секретарша, было бы нежелательно сохранять данные секретарши в 100 экземплярах, когда вполне достаточно одного экземпляра с соответствующей настройкой ссылок (нечто похожее происходит при приведении баз данных к нормальной форме с исключением избыточных данных).

К счастью, в .NET Framework сохранение объектов не требует особых усилий со стороны программиста. Как будет вскоре показано, объекты можно сохранять даже в понятном для человека формате SOAP (Simple Object Access Protocol), основанном на языке XML.




Потоки данных


Как упоминалось во вступительной части, одной из целей проектирования класса System. I0.Stream было абстрагирование примитивных операций при работе с потоками байтов. В соответствий с этой концепцией каждая конкретная реализация класса Stream должна предоставить свои версии следующих методов:

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

Впрочем, этим возможности не ограничиваются. Кроме простого перемещения от первого байта к последнему реализация класса Stream может поддерживать и другие способы — например, перемещение в обратном направлении или непосредственный переход к заданной позиции в потоке. Такое возможно для файловых потоков, но не имеет смысла (а следовательно, и не реализуется) для потоков, основанных на сетевых соединениях. Свойство CanSeek позволяет узнать, поддерживает ли поток произвольный доступ. Если свойство равно True, значит, в производном классе поддерживаются реализации методов Seek и SetLength, а также свойств Position и Length.

В реализации метода Seek обычно используются три значения (Begin, Current и End), входящие в удобный перечисляемый тип SeekOrigin.

В табл. 9.7 перечислены основные методы абстрактного класса Stream, смысл которых должен сохраниться и в производных классах.



Практический пример: динамический список с поддержкой сериализации


Прежде чем приводить полный код примера, мы хотим предупредить об одной потенциальной трудности, которая постоянно возникает при восстановлении сохраненных объектов, а особенно объектов, хранящихся в динамических списках. Итак, после завершения восстановления мы получаем набор обобщенных объектов, хранящихся в динамическом массиве. Но как определить истинный тип этих объектов, чтобы выполнить правильное преобразование? В следующем примере эта информация жестко фиксируется в процессе восстановления, поскольку мы точно знаем порядок занесения объектов иерархии 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




Простая сериализация



Прежде всего импортируйте пространство имен 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



Рекурсивный просмотр дерева каталогов


Класс 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



Сетевые потоки



Среди областей, в которых особенно наглядно проявляются возможности абстрактной модели потока, особое место занимает пересылка информации в Интернете. Работа с низкоуровневым кодом HTML и XML почти не требует усилий со стороны программиста. Хотя в этом разделе мы сможем дать лишь общее представление об этой важной теме и о задействованных пространствах имен, по крайней мере вы увидите, как потоковая интерпретация сетевых данных реализуется на практике. В рассмотренном ниже примере мы передаем информацию на web-сайт и получаем непосредственный HTML-код новой страницы в качестве результата запроса. Анализ полученного HTML-кода приносит нужную информацию.

Мы не смогли устоять перед искушением: наше маленькое приложение обращается на сайт Amazon.com и возвращает текущие сведения о количестве проданных экземпляров нашей книги. Обобщенный алгоритм выглядит следующим образом:

    Создать объект URI (Universal Resource Locator) передачей строкового параметра конструктору класса URI.
    Передать объект URI методу Create класса HttpWebRequest, чтобы инициировать выдачу запроса HTTP.
    Вызвать метод GetResponse класса HttpWebRequest и получить поток.
    Проанализировать полученный поток, содержащий 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, в результате чего символ «,» интерпретировался неверно. Разумеется, проблема легко решается — достаточно, чтобы функция возвращала значение строкового типа.



Члены базового класса


GetFileSystemlnfos Хороший пример использования абстрактных классов: метод возвращает массив объектов FileSystemlnfo, представляющих все файлы и подкаталоги текущего каталога MoveTo(ByVal destDirName As String) Перемещает Directorylnfo и все его содержимое Root (свойство) Объект DirectoryIlnfo для корневого каталога в иерархии текущего каталога



Члены класса Filelnfo


Копирует существующий файл и возвращает объект Filelnfo для копии. Необязательный логический параметр управляет перезаписью существующих файлов Create Создает файл по имени, указанному при конструировании объекта Filelnfo, и возвращает объект FileSystem для нового файла Delete Удаляет файл, представленный объектом FileInfo MoveTo(ByVal destFileName As String) Перемещает файл

Идея выделения общей функциональности в абстрактный базовый класс выглядит впол-не логично, однако в данном случае она реализована не лучшим образом. Например, свдйство Length присутствует в файле FileInfo, но не поддерживается в FileSystemlnfo, поэтому для вычисления размера дерева каталогов приходится прибегать к услугам другого объекта — а именно вызывать метод Size объекта Folder, входящего в модель FileSystemObject. Эта модель впервые была представлена в VBScript, поэтому в решение приходится включать ссылку на библиотеку сценарной поддержки на базе СОМ.



Основные методы класса


В .NET Framework входят классы для работы с XML, спроектированные по образцу класса Stream. Впрочем, пространства имен XML в .NET велики и сложны, и о них вполне можно было бы написать отдельную книгу.



Значения перечисляемого


Объекты 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



Основные методы


Read Читает один символ из входного потока. Перегруженная версия читает в символьный массив определенное количество символов начиная с заданной позиции ReadLine Читает символы до комбинации CR+LF и возвращает их в виде строкового значения. Если текущая позиция находится в конце файла, метод возвращает Nothing ReadToEnd Читает все символы от текущей позиции до конца TextReader и возвращает их в виде одной строки (метод особенно удобен при работе с небольшими файлами)



Важнейшие члены классов FileSystemInfo, FileInfo и DirectoryInfo



Класс FileSystemlnfo является базовым для классов Directorylnfo и Filelnfo и содержит большую часть их общей функциональности. Перед нами хороший пример тех возможностей, которые открываются при использовании абстрактных базовых классов. В классе Directory Info существует метод GetFileSystemlnfos, который возвращает массив объектов FileSystemlnfо, представляющих файлы и подкаталоги заданного каталога. Такое становится возможным только благодаря существованию класса FileSystemlnfo. Важнейшие члены базового класса FileSystemlnf о перечислены в табл. 9.4.



Запись в файл


Начнем с рассмотрения команды, часто встречающейся при работе с файловыми потоками:

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. Учтите, что некоторые из них требуют особых привилегий для операций с файлами.