программирование, создание программ, учебник Delphi, учебник по программированию, бейсек, делфи, си, паскаль
 
 
 

 

Ввод-вывод

В прежних версиях 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

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

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

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

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

В табл. 9.5 и 9.6 перечислены важнейшие методы класса DirectoryInfo и методы класса Filelnfo, не имеющие непосредственного отношения к потокам (эта тема будет рассматриваться позже).

Таблица 9.5. Основные методы класса DirectoryInfo

 
Таблица 9.6. Члены класса Filelnfo, не возвращающие потоков

 

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

Все классы иерархии 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

 

В .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

 

Объекты FHeStream также возвращаются следующими методами классов File и FHelnfo: File.Create, File.Open, File.OpenRead, File.OpenWrite, FHeInfo.Create, FHelnfo.Open, FHelnfo.OpenRead.
Хотя файловые потоки поддерживают произвольный доступ методом Seek, базовый класс-FileStream ориентирован исключительно на операции с байтами, поэтому его возможности ограничиваются простой записью байта или массива байтов методами WriteByte и Write. Приведенный ниже фрагмент создает файл, показанный на  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, а также воспользоваться методом 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

 
Чтение и запись двоичных данных: классы 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)
На  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)
Если вы хотите организовать обобщенное чтение двоичных данных и вас не интересует, какому типу соответствуют прочитанные байты, воспользуйтесь методом 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

Таблица 9.12. Основные методы класса TextWriter

 

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

Впрочем, компонент FileSystemMonitor не всесилен — в частности, он не позволяет отслеживать изменения в самом каталоге. Если кто-то переименует файл, находящийся в каталоге, вы об этом узнаете, однако переименование самого каталога останется незамеченным (конечно, для отслеживания подобных изменений можно дополнительно следить за родительским каталогом).
Компонент Fi1eSystemMoni tor, как и все компоненты разных панелей элементов VS .NET, является конкретной реализацией более общего класса. В данном случае это класс FileSystemWatcher, производный от класса Component. Режим отслеживания подкаталогов включается следующей командой:
FileSystemWatcherl.IncludeSubdirectohes = True
На  6 изображен примерный вид формы. Компонент FileSystemWatcher находится на вкладке Components. Визуального интерфейса он не имеет и поэтому при размещении на форме он отображается на служебной панели, показанной в нижней части  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
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.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика