Сайт Алексея Муртазина (Star Cat) E-mail: starcat-rus@yandex.ru
Мои программы Новости сайта Мои идеи Мои стихи Форум Об авторе Мой ЖЖ
VB коды Статьи о VB6 API функции Самоучитель по VB.NET
Собрания сочинений Обмен ссылками Все работы с фото и видео
О моём деде Муртазине ГР Картинная галерея «Дыхание души»
Звёздный Кот

Самоучитель по VB.NET
Глава 12.

Пространства имен .NET: общие сведения

Дамы и господа, мы начинаем большую экскурсию по пространствам имен Microsoft .NET. Вас сопровождает Дэниел Эпплман, опытный экскурсовод, который не только укажет на основные достопримечательности, но и познакомит с историей предмета начиная с времен Visual Basic 1. Итак, садитесь поудобнее, пристегните ремни и смотрите внимательнее.

 

Главное, что нужно знать о пространствах имен .NET

Всю суть этой главы можно сформулировать в двух предложениях. Их необходимо хорошенько понять и запомнить, даже если вы пропустите все остальное.

.NET Framework

Многое из того, что происходило в VB6, напоминало чудо. В начале работы над проектом программист выбирал тип создаваемого приложения: ActiveX EXE, ActiveX DLL, стандартный ЕХЕ-файл, приложение IIS, элемент управления и т. д. Различия между этими классами приложений в большой степени определялись внутренними механизмами самого VB.

В Visual Basic .NET компилятор определяет лишь синтаксис языка, на котором вы работаете, —и ничего больше.

Позвольте мне еще раз подчеркнуть это обстоятельство.

В Visual Basic .NET никаких чудес нет. Компилятор решает только одну задачу: он обрабатывает группу текстовых файлов с ключевыми словами VB .NET по правилам, определяемым синтаксисом языка.

В новом языке Microsoft C# чудес тоже нет. Собственно, он отличается от VB .NET лишь тем, что обрабатываемые текстовые файлы написаны по правилам синтаксиса С#.

Все чудеса, все эффектные возможности (формы, web-приложения и web-службы, управляющие элементы .NET и компоненты) существуют благодаря

библиотеке классов .NET и Common Language Runtime. Даже поведение компонентов, элементов и окон на стадии конструирования определяется спецификой классов, созданных вами, — классов, производных от классов .NET Framework. Атрибуты, управляющие поведением классов, также создаются на базе классов .NET Framework.

Следует особо подчеркнуть, что выбор языка особой роли не играет. Формы VB .NET и С# содержат более или менее одинаковый код. Все различия сводятся к синтаксису языка, который был использован для определения классов, производных от классов .NET Framework, и вызова их методов.

Я уверен, что большинство книг, посвященных VB .NET, будет начинаться с демонстрации различных вариантов приложений — на базе форм Windows, web-форм, web-служб и т. д. Возможно, такой подход действительно поможет быстрее взяться за практическое программирование, но при этом читатель рискует упустить очень важное обстоятельство. В .NET формы не являются отправной точкой для программирования. Форма — всего лишь результат определения класса, производного от базового класса .NET. Логичнее было бы начать с наследования и объектно-ориентированного программирования, а затем перейти к .NET Framework. После этого изучение форм, web-приложений и различных типов приложений, включая web-приложения, сводится к простому изучению методов базового класса, на основе которого создан ваш производный класс, а также правил расширения этих классов посредством наследования. Именно по этой причине многие приложения, приведенные выше, были консольными. Консольные приложения позволяют сосредоточить все внимание на программном коде, не отвлекаясь на более сложные пространства имен .NET.

Из предыдущих глав вы узнали, чем VB .NET отличается от VB6 на уровне синтаксиса. Практически все, что вам осталось узнать, так или иначе связано с классами .NET Framework и средой Common Language Runtime.

Мы подходим ко второму факту, который необходимо хорошо усвоить...

 

.NET учитывает интересы программистов VB .NET

Давайте поговорим о Windows-программирования вообще, без технологий Microsoft .NET.

Как программисты C++ учатся программировать для Windows?

Большинство начинает с элементарных книг, в которых базовые принципы изложены лучше, чем в документации Microsoft (совершенно не предназначенной для начинающих: MSDN представляет собой технический справочник, а не учебник).

Программисты среднего и высокого уровня, а также работающие с новыми технологиями, еще не описанными в книгах, черпают информацию из двух источников:

1. Документация Microsoft.

2. Специализированная литература по конкретным темам. Они могут читать документацию Microsoft, поскольку она предназначена для программистов C++ — например, в объявлениях функций API приводятся прототипы C++. Работа с многими параметрами функций API в VB6 сильно затруднена. Во многих интерфейсах СОМ используются типы данных, не поддерживаемые в VB61. Любые архитектурные допущения основываются на том, что программист работает на C++, причем результат часто оказывается абсолютно несовместим с VB6.

В результате у большинства программистов Visual Basic нет привычки искать нужную информацию в документации Microsoft. Они предпочитают книги, в которых документация Microsoft «переводится» на язык VB.

Откровенно говоря, я от этого даже выиграл: моя книга «Visual Basic Programmer's Guide to the Win32 API»2стала популярной именно потому, что она отвечала реальным потребностям программистов Win32 API, ведь чтобы эффективно работать с Win32 API, необходимо было много знать.

Но представьте себе, что при разработке Win32 API были бы учтены интересы программистов VB, каждая функция сопровождалась объявлением VB, а среди примеров наряду с программами C++ были бы представлены и версии, написанные на VB6.

Что бы стало с моей книгой?

Во всяком случае, такой успех ей бы и не снился.

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

Но я могу уверенно заявить: если бы Win32 API был спроектирован для программистов VB, абсолютное большинство программистов работало бы, используя документацию Microsoft, и не стало бы покупать мою книгу3.

Мы подходим к самому главному.

Документация .NET Framework написана для программистов Visual Basic .NET. Все классы и все методы могут использоваться из VB .NET4. Синтаксис вызовов VB .NET включен в нее наряду с синтаксисом С# и C++.

Пожалуйста, заведите новую привычку: сначала прочитайте документацию .NET Framework!

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

1Впрочем, продукты независимых фирм, в частности пакет Desaware SpyWorks, позволяют программистам VB6 нормально использовать и реализовывать эти несовместимые интерфейсы.

2Русский перевод: Win32 API и Visual Basic. СПб.: Питер, 2000. -Примеч. ред.

3...А моя карьера пошла бы в совершенно другом, неизвестном направлении...

4Существуют отдельные исключения, несовместимые со спецификацией CLS, — впрочем, пользоваться ими все равно не рекомендуется.

Не поймите меня превратно. Скоро рынок будет завален книгами по .NET Framework. Среди этих книг будет много полезных, которые описывают нетривиальные приемы, содержат более доступные объяснения, материал, ориентированный на начинающих или опытных программистов, или же углубленно рассматривают отдельные типы приложений. Другие книги будут написаны со специфическими целями, например чтобы помочь программистам VB6 перейти на новую технологию1.

Но вам уже не понадобятся книги и статьи с описанием стандартных операций и простых задач. Прослойка между VB и миром «серьезного» Windows-программирования исчезнет.

 

Начинаем экскурсию

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

В этой главе я постараюсь объединить оба подхода.

Ни из путеводителя, ни от экскурсовода вы никогда не получите полной информации обо всем, что вы видите на своем пути. Не ждите этого и от меня.

Я несколько раз начинал писать эту главу. В одной из неудачных попыток я решил привести список всех объектов .NET Framework, привести для каждого объекта комментарий из одной-двух строк и перечислить его методы. На всякий случай я решил оценить объем своей работы. Оказалось, что глобальный кэш сборок содержит свыше 6700 объектов (классов и перечислений)2. Даже если бы каждый объект был представлен всего одной строкой, список занял бы свыше 100 страниц.

Проку от такого списка было бы немного, все свелось бы к обычному пересказу документации (чего я терпеть не могу).

Поэтому данную главу не следует рассматривать как «справочник» по пространствам имен .NET. Я постарался привести общий обзор пространств имен, указать на их взаимосвязи и отдельно подчеркнуть некоторые особенно эффектные возможности. Надеюсь, к моменту чтения этой главы вы уже будете достаточно хорошо представлять себе структуру связей между различными компонентами .NET Framework.

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

Как известно, любая задача считается на 90 % решенной, если вы достоверно знаете, что решение существует. Я постараюсь показать, какими возможностями вы располагаете, чтобы вы знали, в какой области следует искать решение.

1Одну из таких книг вы как раз держите в руках.

2При создании нового Windows-приложения по умолчанию загружается около 2700 объектов. Приведенные цифры были получены при помощи приложения Derivations2, представленного ниже в этой главе.

 

Карта

В любом путеводителе центральное место занимает карта. Наверное, сейчас мне бы следовало нарисовать красивую блок-схему с основными компонентами .NET Framework, однако мои исследования показали, что здесь требуется нечто помощнее. Итак, познакомьтесь с тремя важнейшими инструментами, которые помогут вам ориентироваться в пространствах имен.

Справочная документация MSDN

Документация .NET Framework SDK станет вашим главным источником информации, но основным справочником станет раздел «.NET Framework raquo;. Содержимое этого раздела упорядочено по пространствам имен, а внутри каждого пространства объекты отсортированы в алфавитном порядке.

Справочник лучше всего подходит для поиска подробных описаний конкретных объектов и методов. Вы также можете просмотреть его, чтобы составить общее представление о библиотеке классов, хотя просмотр такого справочника — задача нелегкая и непростая.

Раздел «Programming with the .NET Framework» содержит полезную информацию о конкретных областях .NET. He ограничивайтесь одной документацией VB .NET; помните, что общая документация .NET Framework тоже была рассчитана на программистов VB .NET.

Наконец, не пренебрегайте примерами. Большинство примеров входит в документацию VB .NET, но вы без особого труда разберетесь и в других примерах, поскольку операции с классами .NET Framework выполняются во всех языках практически одинаково.

Утилита WinCV

Утилита WinCV, входящая в .NET Framework SDK, предназначена для просмотра классов. Информация о членах заданного класса извлекается посредством рефлексии.

Используйте утилиту WinCV, если вы обнаружите какие-либо расхождения между документацией и реальной функцией. WinCV поможет вам узнать, из каких членов состоит объект на самом деле1.

1Документация Microsoft чаще страдает от недостаточной глубины и четкости, нежели от фактических ошибок, но и это не исключено, особенно при выходе новых продуктов или сильно обновленных версий.

Проект Derivation2

Разбираясь в пространствах имен, я понял, что главным недостатком существующей документации является ее логическая организация. Группировка методов по пространствам имен хороша для высокоуровневых классов, но неудобна на более низком уровне. Например, как получить список всех возможных исключений, которые могут инициироваться исполнительной средой, или всех атрибутов, используемых при разработке компонентов?

К счастью, сама архитектура .NET Framework поддерживает механизм, позволяющий легко получить ответы на подобные вопросы. Поскольку в основе .NET лежит механизм наследования, объекты естественным образом группируются в соответствии с базовыми классами.

Например, чтобы ответить на вопрос: «Какие исключения могут инициироваться системой?», достаточно запросить список всех объектов, производных от System. Exceptions. SystemException. Результат приведен в табл. 12.1.

Таблица 12.1. Системные исключения (неполный список — только для базовых DLL)

System. AppDomainUnloadedException System.ApplicationException System.ArgumentException System.ArgumentNullException System.ArgumentOutOfRangeException System.ArithmeticException System.ArrayTypeMismatchException System.BadlmageFormatException System.CannotUnloadAppDomainException System.Configuration.ConfigurationException System.Configuration.Install.InstallException. System.ContextMarshalException System.Data.ConstraintException System.Data.DBConcurrency Exception System.Data.DeletedRowInaccessibleException System.Data.DuplicateNameException System.Data.EvaluateException System.Data.InRowChangingEventException System.Data.InvalidConstraintException System.Data.InvalidExpressionException System.Data.MissingPrimaryKey Exception System.Data.NuNullAllowedException System.Data.OleDb.OleDbException System.Data.ReadOnlyException System.Data.RowNotlnTableException System.Data.SqlClient._ValueException System.Data.SqlTypes.SqlException System.Data.SqlTypes.SqlNullValueException System.Data.SqlTypes.SqlTruncateException System.Data.StrongTypingException System.Data.SyntaxErrorException System.Data.TypedDataSetGeneratorException System.Data.VersionNotFoundException System. DivideByZeroException System.DllNotFoundException System.Drawing.Printing.InvalidPrinterException

System.DuplicateWaitObjectException System.EntryPointNotFoundException System.Exception System.ExecutionEngineException System.FieldAccessException System. FormatException System.IndexOutOfRangeException System.InvalidCastException System. InvalidOperationException System.InvalidProgramException System.IO.DirectoryNotFoundException System.IO.EndOfStreamException System.IO.FileLoadException System. IO. FileNot Found Exception System.IO.InternalBufferOverflowException System. lO.IOException System.IO.IsolatedStorageException System.IO.PathTooLongException System.MemberAccess.Exception System. MethodAccessException System.MissingFieldException ' System. Missi ngMemberException System.MissingMethodException System.MuIticastNotSupportedException System.Net.CookieException System.Net.ProtocolViolationException System.Net.Sockets.SocketException System.Net.WebException System.NotFiniteNumberException System.NotlmplementedException System.NotSupportedException System.NullReferenceException System.ObjectDisposedException System.OutOfMemory Exception System. OverflowException System.PlatformNotSupportedException System.RankException System.Reflection.AmbiguousMatchException System.Reflection.CustomAttributeFormatException

System.Reflection.InvalidFilterCriteriaException System. Reflection.ReflectionTypeLoadException System.Reflection.TargetException System.Reflection.TargetlnvocationException System.Reflection.TargetParameterCountException System. Resources. MissingManifestResourceException System. Runtime.InteropServices.COMException System.Runtime.InteropServices.ExternalException System.Runtime.InteropServices.InvalidComObjectException System. Runtime. InteropServices.InvalidOleVariantTypeException System. Runtime.InteropServices.MarshalDirectiveException System.Runtime.InteropServices.SafeArryRankMismatchException System.Runtime.InteropServices.SafeArrayTypeMismatchException System. Runtime.InteropServices.SEHException System.Runtime.Remoting.MetadataServices.SUDSGenerator Exception System.Runtime.Remoting.MetadataServices.SUDS ParserException System.Runtime.Remoting.RemotingException System.Runtime.Remoting.RemotingTimeoutException System.Runtime.Remoting.ServerException System.Runtime.Serialization.SerializationException System.Security.Policy.Policy Exception System.Security.Security Exception IparSystem.Security.VerificationException System.Security.XmlSyntaxException System.ServiceProcess.TimeoutException System.StackOverflowException System. SystemException System.Threading.SynchronizationLockException System.Threading.ThreadAbortException System.Threading.ThreadlnterruptedException System.Threading.ThreadStateException System.ThreadStopException , System.TypelnitializationException System.TypeLoadException System.TypeUnloadedException System.UnauthorizedAccessException System.UriFormatException

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

Помимо поиска классов, производных от заданного, проект Derivation2 выводит иерархию наследования для любого объекта, а также может использоваться для поиска всех объектов, реализующих заданный интерфейс.

Проект Derivation2 действует методом «грубой силы»: при помощи рефлексии он загружает информацию типа для всех объектов, на которые имеются ссылки в домене приложения (проект содержит ссылки на большинство пространств имен .NET). Затем методом линейного поиска во всех иерархиях наследования и списках интерфейсов строится список всех заданных объектов (реализующих интерфейс или наследующих от класса)1.

Как обычно, полный проект находится среди примеров книги. Листинги не приводятся, поскольку этот проект просто демонстрирует некоторые приемы, описанные в главе 11.

В этой главе я буду указывать те места, в которых я бы рекомендовал воспользоваться утилитой Derivation2, чтобы получить карту объектов для дальнейших самостоятельных исследований.

1 Решение не слишком эффективное, но зато легко программируемое. Поначалу меня сильно беспокоила проблема быстродействия, и я был убежден, что мне придется переделывать приложение. Я был приятно удивлен тем, что при большом количествое объектов (по моим оценкам, около 25 000) быстродействие оказалось вполне приемлемым. Очевидно, сказываются усилия Microsoft по оптимизации создания и выполнения операций с объектами.

 

Системные классы

Пространство имен System содержит важнейшие классы, используемые практически в любой программе.

Базовые классы

Все классы пространства имен System строятся на основе трех основных классов.

System. Object

Базовый тип в иерархии наследования любого объекта (как ссылочного, так и структурного типа). Центральное место в классе занимает метод ToString. Даже если этот метод не используется в вашей программе, его рекомендуется переопределять, так как вывод строкового представления класса часто помогает в процессе отладки.

System.Type

Класс представляет тип объекта и широко используется при рефлексии.

System .ValueType

Базовый тип для всех объектов структурного типа. Переопределяет метод Equals для сравнения двух структурных объектов на уровне полей. Такое переопределение вполне логично, поскольку переменные объектов ссылочного типа считаются равными, когда они ссылаются на один объект, а переменные структурного типа — когда они содержат одинаковые данные.

 

Вспомогательные языковые классы

К этой категории относятся классы, которые редко напрямую используются в программах, однако они закладывают основу для работы языков .NET. Например, класс System.Int32 представляет целочисленный примитив в языках VB .NET и С#. С этими классами следует познакомиться по двум причинам: во-первых, если вы знаете, что типы данных языка являются классами .NET Framework, то будете помнить, что для них можно вызывать методы объектов System.Object и System. ValueType; во-вторых, некоторые классы содержат дополнительные методы, которые не имеют встроенных аналогов в языке. Эти методы тоже могут использоваться в программах.

System .Array

Примеры непосредственного использования класса встречаются редко, но вы должны помнить, что массивы VB .NET являются производными от этого базового класса. Обратите внимание на метод Sort, предназначенный для сортировки массивов. Ниже приведен фрагмент из проекта Miscl:

Sub ArrayDemo()

Dim A() As Integer = {5, 4, 10, 2, 1}

Array.Sort(A)

Dim i As Integer

Console.WriteLine ("Array Tests")

For Each i In A

Console.WriteLine (i)

 Next

Console.WriteLine()

 End Sub

 

System. Random

По сравнению с функцией Rnd класс обладает большими возможностями построения случайных чисел. В частности, предусмотрена возможность заполнения буфера случайными данными. За дополнительной информацией об этом классе обращайтесь к главе 9.

System.String

В Visual Basic .NET сохранились строковые функции, традиционно считавшиеся одной из сильных сторон VB. Как правило, при работе со строками используются встроенные функции языка, однако класс String (заложенный в основу строк VB) содержит некоторые интересные методы, о которых следует знать. Эти методы позволяют дополнять строки заданными символами, удалять из них символы, легко преобразовывать строки в символы и символьные массивы, а также производить лексический анализ строк по заданным символам-разделителям. Рассмотрим пример из приложения Miscl:

Public Sub StringSprintDemo()

Dim S As String = "a.b.c.d.e.f"

Dim SeparatorsO As Char = {",",Chars(0)}

Dim SArrayO As String

SArray = S.Split(Separators)

For Each S In SArray Console.WriteLine (S)

Next 

End Sub

Результат:

b

с

e

f

He забывайте, что объекты String являются неизменными.

System.Text (пространство имен)

Пространство имен System.Text содержит ряд дополнительных классов, работающих в тесном взаимодействии с классом String для расширения возможностей обработки текстов.

Самым полезным из этих классов, вероятно, является класс StringBuilder. По многим возможностям он эквивалентен классу String, но не является неизменным. Другими словами, изменения, вносимые в объекты StringBuilder (добавление или удаление символов или любые другие модификации текста), приводят к модификации содержимого объекта StringBuilder. Объекты StringBuilder повышают эффективность построения строковых объектов из нескольких строк меньшего размера (поскольку при этом не приходится создавать несколько временных объектов).

В пространство имен System.Text также входят различные объекты для кодирования и декодирования строк в байтовые массивы. В частности, предусмотрены объекты для преобраgetния строк из ANSI в Unicode и обратно.

 

Дата и время

В следующую категорию входят объекты, предназначенные для работы с датой, временем и временными интервалами.

System. DateTime

Объект предназначен для хранения даты/времени и заменяет тип VB6 Date. Содержит большое количество методов, позволяющих:

System.TimeSpan

Если объект DateTime содержит конкретное значение даты или времени, то объект Time Span содержит временной интервал. Методы этого объекта позволяют инициализировать его на основании различных единиц измерения (часов, минут, секунд, тактов и т. д.). С объектами TimeSpan выполняются операции вычитания, сложения и сравнения.

System. TimeZone

Объект предназначен для работы с информацией о часовых поясах. Позволяет определить локальный часовой пояс, смещение UTC, а также проверить действие режима летнего времени.

 

Системные классы общего назначения

System.AppDomain

Класс предназначен для работы с доменами приложений.

System.Console

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

Консольные приложения используют три потока данных. Они читают данные из входного потока, записывают в выходной поток и выводят сообщения об ошибках в поток ошибок. По умолчанию входной поток ассоциируется с клавиатурой, выходной поток ассоциируется с консольным окном режима командной строки, ошибки тоже направляются в консольное окно. Для работы с этими потоками используются методы Read, ReadLine, Write и WriteLine.

Класс Console позволяет перенаправить любые из этих потоков в объекты классов TextReader или TextWriter (см. ниже).

System. Environment

Класс предназначен для получения информации о процессе, работающем в системе. Он заменяет некоторые функции API, а также и методы объекта Арр в VB6. В частности, при помощи класса System. Environment вы можете узнать:

Ниже приведен простой пример из проекта Miscl:

Sub Envi ronmentDemo()

Console.WriteLine (Environment.Cur rentDirectory)

Console.WriteLine (Envi ronment.OSVersion. ToString)

Console.WriteLine (Envi ronment.SystemDi rectory)

Console.WriteLine() 

End Sub

Результат выглядит примерно так:

D:\CPBknet\Srcl\CH12\Miscl\bin

Microsoft Windows NT 5.0.2195.0 

J:\WINNT\System32

 

System.GC

Класс управляет работой сборщика мусора CLR. Примеры использования класса GC приведены в главе 5.

System. MarshalByRefObject

Базовый класс для всех объектов, которые должны передаваться по ссылке за пределы домена приложения. Как вы узнали из главы 10, домен приложения определяет границы разделения памяти в .NET (тогда как процесс определяет границы разделения памяти в других приложениях Windows). Это означает, что за пределами домена приложения указатели становятся недействительными.

Чтобы вы могли сослаться на объект, находящийся вне домена приложения, создается специальный объект-посредник. Обращения к методам и свойствам этого объекта преобразуются в обращения к методам и свойствам реального объекта — этот процесс называется маршалингом (marshaling). Обычно CLR обеспечивает маршалинг автоматически, не требуя никаких дополнительных усилий с вашей стороны.

Объявление объекта производным от System.MarshalByRefObject сообщает CLR о необходимости создания объекта-посредника; обращения к методам и свойствам этого объекта передаются реальному объекту посредством маршалинга. В другом, реже используемом классе System.ComponentModel .MarshalByValue маршалинг выполняется по-другому — данные объекта сохраняются посредством сериализации и передаются за пределы домена приложения, где по ним создается копия компонента. Механизм сериализации рассматривается ниже в этой главе.

 

Исключения

В документации для каждого метода указано, какие исключения он может инициировать. Исключения делятся на категории, которым соответствуют классы, производные от корневого класса SystemException. Пользовательские типы исключения следует объявлять производными от System.ApplicationException.

Если компоненты .NET используются в СОМ, то классы исключений преобразуются в разные коды НRESULT.

Исключения делятся на две категории: пользовательские и системные. Таким образом, при инициировании исключений программист часто оказывается перед философским выбором: определить собственный тип исключения или же остановиться на системном исключении, если оно подходит для данного случая?

Я полагаю, что если системное исключение достаточно адекватно описывает состояние ошибки, вы вполне можете воспользоваться им. Кроме того, я предпочитаю пользоваться системными исключениями еще и потому, что они хорошо знакомы пользователям моих компонентов. Тем не менее, если системное исключение не отвечает вашим потребностям, я рекомендую определять новые исключения производными от System.ApplicationException, а не от классов иерархии System. SystemException. Чтобы получить список всех системных исключений, воспользуйтесь описанным выше приложением Derivation2.

System. Exception

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

System. Appl ication Exception

Класс, производный от Exception и обладающий теми же возможностями. Используется в качестве базового класса для всех исключений, инициируемых приложением (в отличие от исключений, инициируемых CLR). Все пользовательские типы исключений следует объявлять производными от этого класса, а не от System.Exception.

System. System Exception

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

 

Атрибуты

Атрибуты рассматривались в главе 11. Их значения можно получить во время выполнения программы посредством рефлексии; кроме того, атрибуты влияют на работу компилятора и компонентов среды программирования. Чтобы получить список всех системных атрибутов, воспользуйтесь утилитой Derivation2 для поиска всех классов, производных от System. Attribute. Особого внимания заслуживают следующие категории.

Некоторые важные классы атрибутов перечислены ниже.

System. Attribute

Базовый класс для всех атрибутов.

Общий метод GetCustomAttributes позволяет получить пользовательские атрибуты, связанные с типом. 

System.AttributeUsageAttribute

Атрибут используется при определении новых атрибутов, производных от класса System.Attribute. С его помощью можно указать, должен ли новый атрибут наследоваться производными классами и может ли он устанавливаться многократно.

System.ThreadStaticAttribute

Устанавливается для общих переменных классов или глобальных переменных и указывает на то, что для каждого потока должен создаваться отдельный экземпляр переменной. Такие переменные хранятся в локальной памяти потока.

System. Reflection. AssemblyVersionAttribute

Задает версию сборки. Контроль версии рассматривается в главе 16.

 

Интерфейсы

В пространстве имен System также определяются некоторые часто используемые интерфейсы.

IAsyncResult

Интерфейс реализуется классами, выполняющими асинхронные операции. При помощи этого интерфейса программа может получить информацию о состоянии асинхронной операции или манипулятор, используемый для ожидания завершения асинхронной операции. Обычно интерфейс IAsyncResult присутствует в классах файлового ввода-вывода или сетевых сокетов (чтобы получить список классов, реализующих этот интерфейс, воспользуйтесь утилитой Derivation2).

ICIoneable

Мы уже рассматривали различия между присваиванием объектов ссылочного и структурного типов. По умолчанию присваивание объекту структурного типа осуществляется поверхностным (shallow) копированием (то есть попарным присваиванием переменных). Следовательно, если структура содержит внутренний объект и вы присваиваете ее другой структуре, обе структуры будут ссылаться на одни и те же внутренние объекты.

Для объектов ссылочных типов новой переменной просто присваивается ссылка на исходный объект.

Интерфейс ICIoneable обеспечивает стандартный механизм копирования как ссылочных, так и структурных объектов. При реализации этого интерфейса программист определяет метод Clone, который должен создавать полностью независимую копию исходного объекта. Фрагмент приложения Miscl, приведенный в листинге 12.1, показывает, как это делается.

Листинг 12.1. Реализация интерфейса ICIoneable

e

 Implements ICIoneable

 Public X As Integer

 Public Y As String

Public Function Clone() As Object Implements ICIoneable .Clone

 Dim n As New WillClone()

 n.X = X 

' Наряду с копированием основного объекта

 ' обычно создаются копии внутренних объектов. 

n.Y = String.Copy(Y) 

Return n

 End Function 

End

Public Sub WillCloneDemo()

Dim objl As New WillClone()

objl.Y = "Test"

Dira obj2 As WillClone = objl

If objl Is obj2 And objl.Y Is obj2.

Y Then Console.WriteLine ("Objects are the same")

End If

obj2 = CType(objl.Clone(), WillClone)

If (Not objl Is obj2) And (Not objl.Y Is obj2.Y) And _

(objl.Y = obj2.Y) Then

 Console.WriteLine ("Objects are not the same but strings are equal")

End If

Console. WriteLine()

 End Sub

При вызове метода WillCloneDemo будут выведены оба сообщения.

IComparable

Интерфейс IComparable при сравнении объектов играет примерно такую же роль, как и интерфейс ICloneable при присваивании. Он позволяет определить критерии сравнения двух объектов для сортировки и идентификации объектов на основании их содержимого. Интерфейс IComparable реализуется более чем 400 объектами .NET Framework.

Используйте этот интерфейс в тех случаях, когда объекты должны сравниваться на основании их содержимого.

IDisposable

В главе 6 было показано, что CLR не обеспечивает детерминированного завершения объектов с использованием секций Finalize. С другой стороны, в .NET Framework предусмотрен логически последовательный вариант детерминированного завершения, основанный на взаимодействии контейнеров. Реализуя интерфейс IDisposable, объект фактически сообщает пользователям компонента о том, что перед освобождением компонента они должны вызвать метод Dispose этого интерфейса. Если пользователь этого не сделает, это может помешать нормальной деинициализации компонента.

Интерфейс IDisposable реализуется для тех компонентов, которые должны участвовать в этой схеме. Также следует предусмотреть «страховочную» деинициализацию в обработчике Finalize на случай, если пользователь компонента забудет вызвать метод Dispose.

Visual Studio автоматически генерирует вызов метода Dispose для компонентов, размещаемых в режиме конструирования.

 

Другие интересные системные классы

Некоторые классы из пространства имен System заслуживают внимания хотя бы своими неординарными возможностями.

System. BitConverier

Вас когда-нибудь интересовало, как различные типы данных представляются на физическом уровне? Класс BitConverter позволяет преобразовать любой примитивный тип данных в массив байтов, и наоборот. Следующий пример взят из приложения Miscl:

Sub Bi tConverterDemo()

Dim d As Double = 1.5E+64

Dim myBitArrayO As Byte = Bi tConverter .GetBytes(d)

Console.WriteLine (BitConverter.ToString(BitArray))

Console.WriteLine (BitConverter.ToDoub1e(BitArray, 0))

Console.Wri teLine( 

End Sub

Результат выглядит так:

78-FB-EF-EE-42-3B-42-4D 

1.5E64

System.Uri, System.UriBuilder

Два класса содержат разнообразные методы для операций с URL: построения, преобразования относительных URI в абсолютные, разбиения на компоненты и т. д. Следующий пример из приложения Miscl демонстрирует некоторые возможности класса Sуstem.Uri:

Public Sub URIDemo()

Dim u As New Uri("http://www.desaware.com")

Console.WriteLine (u.Host)

Console.WriteLine (u.Port)

Console.WriteLine (u.Scheme)

 End Sub

Приведенный фрагмент выводит следующий результат:

www.desaware.com

80

http

 

Коллекции

В Visual Basic .NET существует тип Collection, более или менее похожий на объект Collection, к которому вы привыкли (самое принципиальное различие заключается в том, что несуществующий тип Variant в ссылках заменен типом Object).

Сколь бы приятным и знакомым ни выглядел класс Collection, он далеко не исчерпывает всех возможностей программиста. Даже те, кто раньше использовал класс VB Dictionary, найдут много нового в области работы с коллекциями.

Прежде чем рассматривать новые типы коллекций, стоит присмотреться повнимательнее к принципам их работы.

Класс Microsoft .Visual Basic. Collection, как и большинство классов коллекций, реализует следующие три интерфейса.

Ассоциативные коллекции реализуют интерфейс System.Collections. IDictionary. К этой категории относятся коллекции, в которых хранятся пары «ключ/значение» с возможностью быстрого поиска по ключу и обеспечением уникальности ключей.

 

System. Collection Base и пользовательские коллекции

При программировании открытых коллекций я всегда рекомендую использовать сильную типизацию элементов. Это избавит вас от необходимости включать в программу проверку ошибок на случай включения в коллекцию посторонних объектов, с которыми программа не умеет работать.

В Visual Basic .NET создание типизованных коллекций заметно упрощается. Все, что от вас потребуется, — это объявить класс коллекции производным от класса CollectionBase, спроектированного специально для этой цели. Проект CollectionDemo (листинг 12.2) показывает, как это делается.

Листинг 12.2. Файл Modulel.vb проекта CollectionDemo

' Использование For...Each при работе с коллекциями 

' Copyright ©2001 by Desaware Inc. 

All Rights Reserved

Module Modulel

Enum MyFish

OneFish

TwoFi sh

RedFish

BlueFish

End Enum

Public lection 

Inherits CollectionBase

 Public Function Add(ByVal value As MyFish) As Integer

MyBase.List.Add (value)

 End Function 

Public Sub Insert(ByVal index As Integer, ByVal value As MyFish)

List. Insert(index, value)

 End Sub

Public Function IndexOf(ByVal value As MyFish) As Integer

Return List. IndexOf(value)

 End Function

Public Function Contains(ByVal value As MyFish) As Boolean

Return List.Contains(value) 

End Function

Public Sub Remove(ByVal value As MyFish)

List.Remove (value)

 End Sub

Public Sub CopyTo(ByVal array() As MyFish, ByVal index As Integer)

List.CopyTo(array, index)

 End Sub

Default Property Item(ByVal index As Integer) As MyFish

 Get

Return CType(MyBase.List.Item(index), MyFish) 

End Get

  Set(ByVal Value As MyFish)

MyBase.List. Item(index) = value

 End Set 

End Property

Protected Overrides Sub OnInsert(ByVal index As Integer, _

 ByVal value As Object)

If Not TypeOf (value) Is MyFish Then

 Throw New ArgumentException("Invalid type")

End If

 End Sub

Protected Overrides Sub OnSet(ByVal index As Integer, _ 

ByVal oldValue As Object. ByVal newValue As Object) 

If Not TypeOf (newValue) Is MyFish Then

Throw New ArgumentException("Invalid type")

 End If 

End Sub

Protected Overrides Sub OnValidate(ByVal value As Object)

 If Not TypeOf (value) Is MyFish Then

Throw New ArgumentException("Invalid type")

 End If

 End Sub 

End

Sub Main()

Dim col As New Collection()

Console.WriteLine ("VB Collection Type is: " & _ col.GetType.FullName)

Dim F As New FishCollection()

Dim U As IList

F.Add (MyFish.OneFish)

F.Add (MyFish.TwoFish)

F.Add (MyFish.RedFish)

F.Add (MyFish.BlueFish)

il = F

Try

il.Add ("Something not a fish")

 Catch e As Exception

Console.WriteLine (e.Message) 

End Try

Dim afish As MyFish 

For Each afish In F

Console.WriteLine (afish.ToString) 

Next

afish = F(l)

Console.WriteLine (afish.ToString)

 Console.ReadLine()

 End Sub 

End Module

В программе определяется перечисляемый тип с именем MyFish. Коллекция FishCollection предназначена только для работы с объектами этого типа. Методы Add, Insert, IndexOf, Contains, Removes и СоруТо принимают только объекты типа MyFish и ограничиваются простым вызовом соответствующего метода базового класса, принимающего любые объекты. Свойство Item является свойством по умолчанию (допустимо для параметризованных свойств) и также передает обращение свойству Item базового класса.

Помимо реализации методов коллекции, мы также переопределяем методы Onlnsert, OnSet и OnValidate. Эти методы предоставляются базовым классом, чтобы производный класс мог выполнить дополнительную проверку перед выполнением операции. В приведенном примере мы убеждаемся в том, что объект действительно относится к правильному типу.

Зачем это нужно?

А что произойдет, если для ссылки на класс будет использована переменная типа IList? Ссылка будет относиться к реализации интерфейса, принадлежащей базовому классу, что позволит сохранять в коллекции объекты других типов. Наш класс спроектирован таким образом, чтобы производный класс выполнял дополнительную проверку типа, тем самым закрывая эту «лазейку».

 

Подробнее о коллекциях

Любой объект можно наделить возможностью перебора содержимого при помощи синтаксической конструкции For...Each. Для этого класс реализует интерфейс IEnumerable. В листинге 12.3 продемонстрировано использование IEnumerable на примере проекта ForEach.

Листинг 12.3. Реализация интерфейса innumerable

Public llection 

Implements lEnumerable

Private DummyDataO As String = {"One Fish", "Two Fish", _ 

"Red Fish", "Blue Fish")

Function GetEnumerator() As lEnumerator Implements _

 lEnumerable.GetEnumerator

Dim myEnumerator As New Enumerator

ReDim myEnumerator.Snapshot(UBound(DummyData))

DummyData.CopyTo(myEnumerator.Snapshot, 0)

Return myEnumerator 

End Function

Public or

 Implements IEnumerator

Dim Currentlndex As Integer = -1

 Public Snapshot() As String

' Перейти в начало (перед первым элементом)

 Sub Reset() Implements IEnumerator.Reset

Currentlndex = -1 

End Sub

Readonly Property Current() As Object Implements lEnumerator.Current

Get

Return (Snapshot(Currentlndex))

End Get

 End Property

Function MoveNext() As Boolean Implements IEnumerator .MoveNext

Currentlndex += 1

Return (Currentlndex <= UBound(Snapshot))

 End Function

End

Класс PseudoCollection содержит массив строк, содержимое которого должно перебираться в цикле For...Each. Прежде всего необходимо реализовать интерфейс IEnumerable. Этот интерфейс предоставляет единственный метод GetEnumerator, который возвращает ссылку на отдельный объект Enumerator, реализующий интерфейс IEnumerator.

При вызове GetEnumerator текущее состояние данных сохраняется в массиве Snapshot, содержащем копию данных на момент вызова функции GetEnumerator. Перебор данных, хранящихся в массиве, осуществляется методами интерфейса IEnumerator: Reset, Current и MoveNext.

Конечно, при желании можно создать пользовательскую коллекцию с самостоятельной реализацией IList, IComparable и ICollection, но в большинстве случаев двух продемонстрированных решений оказывается вполне достаточно.

 

Другие коллекции

В начале этого раздела я упомянул о том, что помимо класса коллекций Visual Basic (Microsoft .VisualBasic .Collection) существуют и другие варианты.

В пространствах имен System. Collections и System. Collections .Specialized определено несколько типов коллекций. В листинге 12.4 продемонстрирован пример использования класса SortedList из приложения ForEach.

Листинг 12.4. Использование класса SortedList

Dim I() As Integer = {3, 8, 4, 6, 10, 7, 9}

 Dim С As New Collections.SortedList() 

Dim P As New PseudoCollection()

Dim x As Integer

For x = 0 To UBound(I)

C.Addd(x), I(x)) 

Next

Dim Integerlterator As Object

Console.WriteLine ("Array iteration")

 For Each Integerlterator In I

Console.Write (Integerlterator.ToString & ", ")

 Next 

Console.WriteLine (ControlChars.CrLf & "Collection iteration")

Dim Dictlterator As DictionaryEntry

 For Each Dictlterator In С

Console.Write (Dictlterator.Value.ToString & ", ")

 Next

Dim Stringlterator As String

Console.WriteLine (ControlChars.CrLf & "Custom enumerable object")

For Each Stringlterator In P

Console.Write (Stringlterator.ToString & ", ")

 Next

Console. ReadLine()

Несомненно, коллекция с автоматической сортировкой содержимого заслуживает внимания. В оставшейся части этого раздела будут представлены другие примеры коллекций.

Приведенный ниже список не полон. Я рекомендую самостоятельно исследовать пространства имен System.Collections и System.Collections.Specialized. Помните, что большинство классов коллекций небезопасно по отношению к потокам.

System. Col lection .Array List

Удобная разновидность коллекций общего назначения.

System.Collections.BitArray

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

Но самая интересная особенность этого объекта заключается в том, что он может инициализироваться байтовым массивом. Это позволяет взять произвольный тип данных (вероятно, преобразованный классом System.BitConverter) и проверить и/или установить в нем отдельные биты.

System.Collections.DictionaryBase

Используется для создания ассоциативных массивов с сильной типизацией.

System. Collections.HashTable

Класс реализует хэш-таблицу — эффективную разновидность ассоциативных коллекций, обеспечивающую быстрый доступ к любому объекту.

System.Collections.Queue

Реализует очередь (принцип FIFO — «первым пришел, первым вышел»).

System.Collections.SortedList

Ассоциативная коллекция, в которой элементы сортируются по значению ключа.

System. Collections.Stack

Стек (принцип LIFO — «последним пришел, первым вышел»).

System.Collections.Specialized.BitVector

Объект позволяет разделить 32-разрядное число на несколько полей, к каждому из которых можно обращаться напрямую. Читателям, знакомым с C++, он напомнит битовые поля в C++.

System. Collections.Specialized. String

Коллекция для работы со строками, безопасная по отношению к типам.

 

Вывод

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

 

Если вы привыкли использовать графические методы VB6...

Забудьте все, что вы знали о графическом выводе, в том числе и о работе с растровыми изображениями. Графические примитивы Visual Basic (такие, как Line, Circle и Р5ЕТ) ушли навсегда и больше не вернутся. Пропало даже простейшее присваивание при копировании изображений1:

 Picture1.Picture = Picture2.Image

1Свойство Image графического поля (picture box) аналогично свойству Picture VB6, однако оно возвращает ссылку на ранее присвоенное изображение вместо текущего содержимого окна.

Забудьте о дурацкой необходимости измерять расстояние в твипах, что приводило к ошибкам округления при позиционировании элементов и выборе размера шрифта. Отныне практически во всех операциях расстояния задаются в пикселах. Исчезли такие свойства, как ScaleMode, ScaleWidth и ScaleHeight. Конечно, нетривиальные режимы масштабирования по-прежнему доступны, но для работы с ними используются средства .NET Framework.

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

В нескольких ближайших разделах я помогу вам правильно взяться за дело.

 

Если вы привыкли выполнять графические операции средствами Win32 API...

Забудьте все, что вы знали о графическом выводе.

Шутка.

На самом деле вам повезло, и вы очень быстро освоите новые графические возможности. А если вы к тому же прочитали описание графических функций в моей книге «Visual Basic Programmer's Guide to the Win32 API», то сможете быстро просмотреть несколько ближайших разделов, обращая внимание лишь на изменения относительно Win32 API.

 

GDI умер, да здравствует GDI+

Сокращение GDI означает «интерфейс графического устройства» (Graphics Device Interface). В доисторические времена2 программа, пожелавшая вывести что-либо на экран или принтер, должна была располагать полной информацией об устройстве вывода, в том числе знать размеры поверхности вывода и набор управляющих команд. Таким образом, в каждую программу приходилось включать самостоятельную реализацию графического вывода для разных устройств при разных вариантах разрешения.

Библиотека GDI обеспечивает аппаратно-независимый подход к графическому выводу. Приложения работают с универсальными графическими методами, a GDI в сочетании с драйверами устройств Windows выводят изображение на устройство, будь то принтер, окно или метафайл (записанная последовательность графических команд, которая может воспроизводиться приложением). Приложение может ограничиться конкретными значениями разрешения и размера, а может определить логическое окно со стандартной или пользовательской системой координат и поручить GDI его отображение в конкретную область устройства.

2Эпоха DOS, период с конца 70-х годов до начала 90-х.

Термин «контекст устройства» (DC, device context) относится к числу важнейших понятий GDI. Контекст устройства представляет собой объект, ассоциирующий графические команды с устройством вывода. В Windows его можно получить для любого устройства вывода. Все графические команды GDI работают с контекстами устройств (рис. 12.1).

Рис. 12.1. Графические операции в GDI

Результаты выполнения традиционных графических функций Win32 API зависят от объектов GDI (перьев, кистей, шрифтов и растров), «выбранных» в контексте устройства. Таким образом, в Win32 контексты устройств обладают определенным состоянием, зависящим от текущего набора выбранных объектов. Программа должна перевести контекст устройства в нужное состояние перед выводом и, что еще важнее, — восстановить прежний контекст после завершения вывода, чтобы не мешать работе других частей программы. Это особенно важно при вызове функций GDI в Visual Basic 6. Если при вызове функций API программа не восстановит прежнее состояние контекста, это может привести к нарушению работы обычных графических команд VB6.

В .NET Framework на смену GDI приходит GDI+.

Главное различие между GDI и GDI+ заключается в том, что графический вывод теперь не зависит от текущего состояния. Аналогом контекста устройства в GDI+ является объект System.Drawing.Graphics (рис. 12.2). Используемые перья, кисти и шрифты задаются непосредственно при вызове графических команд. Вам уже не придется беспокоиться о том, что вызов графических функций может привести к побочным эффектам.

Рис. 12.2. Графические операции в GDI+

Следующий фрагмент приложения GraphicsDemo демонстрирует использование объектов Graphics, Brush, Pen и Font. 

Private Sub cmdDraw_Click(ByVal sender As System/Object, _

ByVal e As System.EventArgs) Handles cmdDraw.Click

Dim g As Graphics

' Также можно использовать стандартные кисти.

Dim b As SolidBrush = New SolidBrush(color.Beige)

g'= pictureBoxlQ .CreateGraphicsO ' Получить объект Graphics

g.FillRec tangle (b, pictureBoxlQ . ClientRectangle)

' Конструктор для быстрой модификации существующих шрифтов

Dim f As Font = New Font(Me.Font, FontStyle.Bold)

g.DrawStringC'Some Text", f, brushes. Black, 10, 10)

g.DrawRectangle(pens.Blue, 50, 50, 50, 50)

g.DrawLine(pens.Red, 50, 50, 150, 150)

g.Dispose() ' He забывайте о вызове Dispose!

b.Dispose()

f .Dispose()

 End Sub

Метод CreateGraphics класса System.Windows . Forms.Control возвращает объект Graphics, предназначенный для вывода в элементе управления. Все элементы пользовательского интерфейса Windows (в том числе и формы) определяются производными от System.Windows. Forms .Control, поэтому они всегда поддерживают этот метод.

На основе базового класса System.Drawing.Brush определяются кисти разных типов, от текстурных до однородных. Мы используем однородную кисть, созданную на базе одного из стандартных цветов, определенных в классе System. Drawing. Colors. Конструктор шрифта, использованный в нашем примере, берет за основу уже существующий шрифт и изменяет атрибуты его начертания. Существует много конструкторов для создания шрифтов.

Класс System.Drawing.Graphics содержит большое количество графических методов. Обратите внимание: при вызове каждого метода указываются перья, кисти и шрифты, необходимые для выполнения графической операции. Объект Graphics не поддерживает информации о состоянии.

Объекты Brush, Font, Pen, Graphics и другие объекты GDI+ представляют собой «обертки» для базовых объектов GDI, поэтому разработчики должны были предусмотреть средства для освобождения последних. Все перечисленные объекты реализуют интерфейс IDisposable, поэтому после завершения графического вывода следует вызвать метод Dispose. Впрочем, если вы забудете это сделать, ничего особо страшного не произойдет. Метод Finalize этих объектов автоматически освободит базовые объекты GDI, однако вызов Dispose работает эффективнее.

Преимущества GDI+ перед GDI не исчерпываются отсутствием зависимости от состояния. В GDI+ поддерживаются такие графические операции, как наложение (blending) и сглаживание (anti-aliasing), а также изощренные алгоритмы масштабирования растровых изображений. Большинство новых возможностей сосредоточено в пространстве имен System.Drawing.Drawing2D. Ниже приведен простой пример: заполнение формы градиентной заливкой.

Private Sub cmdGradient_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles cmdGradient.Click

 Dim Ib As New Drawing2D.LinearGradientBrush(Me.DisplayRectangle, _

  color.Blue, color.Red, Drawing2D.LinearGradientMode.Horizontal)

 Dim g As Graphics = Me .CreateGraphics()

g.FillRectangle(Ib, Me.DisplayRectangle)

 Ib.DisposeO g.Dispose() 

End Sub

 

Растровые изображения

Класс System. Drawing. Bitmap предназначен для загрузки растровых изображений в разных форматах и из разных источников. Для полученного растра можно создать объект Graphics, упрощающий вывод в этом растре. Объект Bitmap также позволяет задавать значения отдельных пикселов (замена команды PSet VB6). Данная возможность продемонстрирована во фрагменте приложения GraphicsDemo, приведенном в листинге 12.5.

Листинг 12.5. Операции с растровыми изображениями

Private Sub cmdBitmap_Click(ByVal sender As System.Object._

 ByVal e As System.EventArgs) Handles cmdBitmap.Click

Dim g, g2 As Graphics

g = pictureBoxl () .CreateGraphics()

Dim bm As New Bi tmap(pictureBoxK) .ClientRectangle .Width , _

pictureBoxK).ClientRectangle.Height, g)

g2 = Graphics.Fromlmage(bm)

dwgraphics.Copylmage(g, g2, pictureBoxlQ.ClientRectangle.Width, _

pictureBoxK) .ClientRectangle. Height)

g2.DrawLine(pens.Green, 60, 50, 160, 150)

  bm.SetPixeld, 1, color.Red) 

bm.SetPixeld, 2, color.Blue)

  bm.SetPixel(1, 3, color.Green)

1 Другой способ получения графических объектов

g = Graphics.FromHwnd(pictureBox2().Handle)

Dim g3 As Graphics = graphics.FromHwnd(pictureBox2().Handle)

g3.Drawlmage(bm, 0, 0, pictureBox2().DisplayRectangle.Width, _

pictureBox2().DisplayRectangle.Height)

g.Dispose()

g2.Dispose()

g3.Dispose() '

bm.Dispose()

End Sub

В GDI+ не предусмотрено простой возможности копирования растровых изображений из существующих окон. Никаких объяснений, кроме невероятного просчета разработчиков, предложить не могу. В листинге 12.6 приведен .простой класс, копирующий текущее содержимое окна в объект Graphics, представляющий растровое изображение или другое окно. Копирование осуществляется традиционной функцией API BitBlt.

Листинг 12.6. Копирование изображения в окне функцией BitBlt

Private Module APIDent>

 Friend Const SRCCOPY As Integer = &HCC0020&

Friend Deunction BitBlt Lib "gdi32" (ByVal hDestDC_

 As IntPtr, ByVal x As Integer, ByVal у As Integer, _

ByVal nWidth As Integer, ByVal nHeight As Integer, _ 

ByVal hSrcDC As IntPtr, ByVal xSrc As Integer,

 ByVal ySrc As Integer, ByVal dwRop As Integer) As Integer 

End Module

Public cs

Shared Sub CopyImage(ByVal Source As System.Drawing.Graphics, _ 

ByVal Dest As System.Drawing.Graphics, ByVal Width As Integer, _

 ByVal Height As Integer)

Dim dhdc, shdc As IntPtr

dhdc = Dest.GetHdc

shdc = Source.GetHdc

BitBlt(dhdc, 0, 0, Width, Height, shdc, 0, 0, SRCCOPY)

Dest.ReleaseHdc (dhdc)

Source.ReleaseHdc (shdc)

 End Sub 

End

Могу лишь предположить, что в окончательной версии этот недостаток будет устранен, поскольку графическими функциями API пользоваться не рекомендуется (причины описаны в главе 15).

 

Стратегии изучения GDI+

Как было сказано выше, по GDI+ вполне можно написать отдельную книгу. Тем не менее основные правила графического вывода просты.

  • Получите объект System. Drawing.Graphics для той поверхности, на которой вы хотите выводить.
  • Создайте перья, кисти, шрифты и другие объекты, используемые командами вывода.
  • Воспользуйтесь графическими методами объекта Graphics для вывода на поверхности.
  •  После завершения вывода освободите все объекты методом Dispose.

Мой совет: обратитесь к документации и внимательно прочитайте описания классов следующих пространств имен.

  •  System.Drawing — все базовые классы GDI+.
  •  System. Drawing. Design — классы диалоговых окон для загрузки растровых изображений, выбора шрифтов и других типовых операций пользовательского интерфейса, используемых при графическом выводе.
  •  System. Drawing. Drawing2D — классы для выполнения более сложных графических операций (в частности, наложения и градиентных заливок).
  •  System. Drawing. Imaging — классы поддержки метафайлов и нетривиальных графических операций (например, преобразований цветовых пространств).
  •  System.Draw ing.Printing — классы поддержки печати (этой теме посвящен следующий раздел).

Утилита Derivation2 поможет вам лучше разобраться в иерархии объектов в этих пространствах. Например, проверка класса System. Drawing. Brush показывает, что этот класс является базовым для следующих классов:

System.Drawing.Drawing2D.HatchBrush

System.Drawing.Orawing2D.LinearGradlentBrush

Systern.Drawing.Drawing2D.PathGradlentBrush

System.Drawing.SolidBrush

System.Drawing.ТехtureBrush

Печать

Само название пространства имен печати, System. Draw ing. Printing, подсказывает принцип его работы. Как только что было сказано, GDI+ заставляет программистов VB6 перейти от старомодных графических команд VB на новый стиль графического программирования, знакомый программистам Win32 API. Вероятно, вас не удивит, что аналогичные изменения произошли и в области печати.

Суть этих изменений можно выразить всего четырьмя словами: объекта Printer больше нет.

Нет, он не был переименован. Исчезла сама концепция объекта печати.

Не паникуйте. Стоит усвоить несколько базовых принципов, и вы поймете, что подход .NET к печати невероятно прост и удобен — и при этом обладает огромными возможностями.

В этом разделе я постараюсь представить печать в .NET в нужном ракурсе, а остальное вы найдете в документации.

На рис. 12.3 изображена простейшая модель печати, использовавшаяся в VB6.

Рис. 12.3. Печать в VB6

Вы передаете на принтер команды создания документа, вывода графики и текста, формирования новой страницы и фактического начала печати. В .NET используется более сложная модель, показанная на рис. 12.4.

Рис. 12.4. Печать в .NET

И я еще говорю об упрощении модели печати?!

Да, в большинстве случаев. Присмотритесь повнимательнее.

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

На происходящее можно взглянуть иначе. Печать трех страниц в VB6 описывается следующим фрагментом псевдокода:

Sub Print

Напечатать страницу 1

Напечатать страницу 2

Напечатать страницу 3 

End Sub

В VB .NET аналогичный фрагмент выглядит иначе:

Sub Print

Печать документа End Sub

Sub PrintDocument_PrintPage Handles событие PrintPage документа

Напечатать страницу

Вернуть признак продолжения печати 

End Sub

В проекте PrintingDemo эта абстрактная схема преобразуется в реальный код.

На форме находится текстовое поле для ввода печатаемой строки. Мы несколько упрощаем свою задачу и используем для печати шрифт, ассоциированный с текстовым полем. Конечно, вместо него можно воспользоваться любым объектом Font (в том числе и выбранным в стандартном диалоговом окне System.Windows . Form. FontDialog). На форме также присутствует графическое поле с растровым изображением.

Печать начинается с создания объекта System.Drawing. Printing. PrintDocument. С этим объектом связаны два других объекта, PrinterSettings и PrintController. Объект PrinterSettings определяет принтер и параметры печати. Объект PrintController непосредственно управляет печатью. Обычно при печати используется стандартный объект PrintController, но вы можете определить производный класс для нестандартной печати, например для отображения статусной информации в процессе печати.

В нашем примере для настройки параметров печати (объект PrintSettings) используется стандартное диалоговое окно, знакомое каждому пользователю Windows. Для работы с этим окном используются объекты PrintDialog. При выходе из вызова prDialog.ShowDialog объект PrinterSettings соответствует принтеру, выбранному в окне.

Таким образом, в VB .NET печать на любом доступном принтере производится элементарно — вам не придется беспокоиться о том, какой принтер выбран по умолчанию. Естественно, выбрать принтер и изменить настройки конкретного задания печати можно и без диалогового окна, простыми операциями с соответствующими объектами из пространства имен System.Drawing. printing.

Задание печати создается вызовом prDoc.Print. Но перед вызовом этого метода необходимо указать событие, которое должно инициироваться для каждой страницы. Как было показано в главе 10, событие в действительности является делегатом и при помощи команды AddHandler элементарно связывается с любым методом — в нашем примере это метод PagePrintFunction.

Private Sub cmdPrint_Click(ByVal sender As System.Object,_

 ByVal e As System.EventArgs) Handles cmdPrint.Click 

Dim prDialog As New PrintDialog() 

Dim prDoc As New Drawi ng. Printing. Pri ntDocument()

 prDoc.DocumentName = "My new printed document"

 prDialog.Document = prDoc prDialog.ShowDialog()

' Подключить событие, вызываемое для каждой страницы 

AddHandler prDoc.PrintPage, AddressOf Me.PagePrintFunction

 prDoc.Print() 

prDoc. Dispose()

 prDialog.Dispose()

 End Sub

Метод PagePrintFunction (обработчиксобытия PrintDocument .PrintPage) получает два параметра: ссылку на объект Pri ntDocument (отправитель) и ссылку на объект PrintPageEventArgs, содержащий большое количество свойств для получения сведений о печатаемой странице.

Важнейшим из этих свойств является объект Graphics. Да, все верно — это тот самый объект Graphics, упоминавшийся в предыдущем разделе. В нашем примере выводятся две строки текста: данные ограничивающего прямоугольника (для наглядного представления координат, используемых страницей) и содержимое текстового поля. Обратите внимание на вызов метода GetHeight объекта Font, при помощи которого мы получаем высоту строки текста на текущей поверхности устройства. Метод Drawlmage масштабирует растровое изображение по свободной части страницы (при запуске этой программы приготовьтесь к тому, что печать займет много времени, особенно на принтерах PostScript). Задавая значение свойства PrintPageEventArgs . HasMorePages, можно сообщить объекту PrintController, будут ли печататься дополнительные страницы1.

1В данном примере эта возможность не используется.

Public Sub PagePrintFunctionCByVal sender As Object,_

 ByVal e As Printing.PrintPageEventArgs)

 Dim LineHeight, LineNumber As Single

 LineHeight = txtText().Font.GetHeight(e.Graphics)

Dim TextRect As New RectangleF(0, LineHeight * LineNumber,

e.PageBounds.Width, e.PageBounds.Height - LineHeight * LineNumber)

Dim SF As New StringFormat(StringFormatFlags.LineLimit)

e.Graphics.DrawString(e.PageSettings.Margins.ToString,

txtText().Font, brushes .Black, TextRect)

LineNumber += 2 .

e.Graphics.DrawString(txtTextО.Text, txtText().Font,

Brushes.Black, 0, LineHeight * LineNumber)

LineNumber += 1

e.Graphics.DrawImage(pictureBoxK).Image, 0,_

 LineNumber * LineHeight, e.PageBounds.Width,

 e.PageBounds.Height - LineNumber * LineHeight) 

End Sub

Прежде чем двигаться дальше, следует учесть два обстоятельства.

  •  Делегаты определяют PagePrintFunction и другие обработчики событий объектов PrintDocument и PrintController. Как говорилось выше, делегат может связываться с конкретным экземпляром класса. Таким образом, в приложениях, работающих с несколькими документами, делегата можно связать с конкретным экземпляром класса документа и использовать переменные этого класса для отслеживания служебной информации (например, количества страниц).
  •  Поскольку во всех операциях вывода вместо конкретного графического устройства используется объект Graphics, вы можете написать обобщенные графические функции, передавать им объект GraphicsB качестве параметра и использовать их как для печати, так и для вывода на экране.

Последняя возможность продемонстрирована в обработчике cmdPreview_ Click приложения PrintingDemo.

Private Sub cmdPreview_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles cmdPreview.Click

Dim previewDialog As New PrintPreviewDialog()

Dim prDoc As New Drawing. Printing. PrintDocument()

prDoc.DocumentName = "My new printed document"

' Подключить событие, вызываемое для каждой страницы

AddHandler prDoc.PrintPage, AddressOf Me.PagePrintFunction

previewDialog.Document = prDoc

previewDi alog.ShowDialog()

prDoc.Dispose()

previewDialog.Dispose()

 End Sub

Объект PrintDocument создается точно так же, как было показано выше. Вызов диалогового окна Pri ntDi alog пропускается (хотя с его помощью можно направить вывод на другой принтер для предварительного просмотра). Далее мы создаем объект PrintPreviewDialog, обеспечивающий стандартное окно предварительного просмотра, и указываем, что для вывода должен использоваться объект PrintDocument. Объект PrintDocument связывается с методом PagePrintFunction, описанным выше.

При вызове метода Show для объекта PrintPreviewDialog на экране появляется предварительное изображение вашего документа, причем для этого используется тот же программный код, что и при непосредственном выводе на печать!

Завидуй, VB6!

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

За дополнительной информацией о печати в .NET обращайтесь к описанию объектов пространства имен System.Drawing. Printing.

 

Ввод-вывод

 В VB .NET сохранена поддержка традиционных файловых команд BASIC (Get, Put, Print* и т. д.) и традиционных операций файловой системы (CurDir, ChDir, Kill и т. д.). Все эти команды входят в пространство имен Microsoft. VisualBasic.

Конечно, никто не запрещает вам пользоваться этими командами, если вы к ним привыкли, но все же я советую познакомиться с пространством имен System. I0. При помощи объектов этого пространства можно выполнять все традиционные файловые операции VB, но, что еще важнее, — в нем предусмотрены объекты для других типов операций ввода-вывода. Объекты System. 10 применяются в широком спектре приложений, от сериализации до криптографии и сетевой пересылки данных.

Впрочем, использование средств System. I0 связано с определенными затруднениями. Дело в том, что в .NET Framework принят новый подход к вводу-выводу, незнакомый многим программистам VB6 (и даже программистам C++). На нескольких ближайших страницах я постараюсь представить основные принципы ввода-вывода в .NET, чтобы вы поняли логику взаимодействия этих объектов и научились успешно пользоваться ими на практике.

Главный принцип ввода-вывода в .NET понять несложно: по аналогии с тем, как GDI реализует абстрактный уровень вывода на разных графических поверхностях, классы System. 10 реализуют абстрактный уровень чтения и записи для разных устройств ввода-вывода.

Большинство классов, представляющих устройства ввода-вывода, являются производными от класса System. I0. Stream. Этот класс интерпретирует устройство как поток байтов (доступный для чтения или записи) и позволяет выполнять следующие операции:

  •  чтение одного или нескольких байтов данных;
  •  запись одного или нескольких байтов данных;
  •  асинхронное чтение или запись (с дополнительной возможностью оповещения о завершении операции);
  •  физическая запись данных из промежуточного буфера на устройство;
  •  переход к заданной позиции в потоке данных;
  •  закрытие потока (устройства) после завершения всех операций.

Конечно, для некоторых устройств поддерживается лишь часть операций, но вы можете проверить факт поддержки той или иной операции для каждого конкретного устройства.

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

Некоторые классы потоковых устройств изображены на рис. 12.5.

Классы, показанные на рисунке, представляют файлы, блоки памяти, зашифрованные данные, сокеты, сетевые данные HTTP, данные XML и т. д. Полный

список классов, производных от System. I0. Stream, можно получить при помощи утилиты Derivation2.

Рис. 12.5. Ввод-вывод в .NET

Каждый поток может быть связан с объектом ButferedStream. Буферизованный вывод повышает быстродействие при выполнении большого числа коротких операций ввода-вывода, например при посимвольном чтении данных из устройства. Объект BufferedStream обеспечивает промежуточный буфер заданного размера для уменьшения количества обращений к физическому устройству.

Хотя операции чтения и записи могут выполняться непосредственно с потоками, в вашем распоряжении также находятся специализированные классы, производные от класса TextReader. Этот класс оптимизирован для обработки текста. Например, объект StreamReader поддерживает чтение текста по строкам (до следующего признака конца строки), а класс XMLTextReader позволяет интерпретировать поток как данные в формате XML.

Объявляя классы производными от TextReader, можно определять специализированные объекты для работы с потоковыми данными.

Класс System. I0. Stream позволяет загрузить один или несколько байт в байтовые переменные или массивы. Объект Binary Reader специализируется на чтении, произвольных данных; он позволяет интерпретировать поток данных как источник любых примитивных типов данных. Таким образом, вы можете читать данные непосредственно в переменные Boolean, Integer, Long, String и других примитивных типов.

Не стоит и говорить, что все сказанное выше о чтении данных в равной степени относится и к записи. Для этого используются объекты TexWriter, StringWriter, BinaryWriten, StreamWriter и т. д.

В приложении IODemo (листинг 12.7) показано простейшее применение этих базовых принципов на примере функции ShowTheStream, успешно обрабатывающей данные двух разнотипных потоков.

Листинг 12.7. Приложение IODemo

' Ввод-вывод

' Copyright ©2001 by Desaware Inc.

Imports System.10

Module Modulel

Const StringToReadFrom As String = "This is a string to read with a string reader"

Private Sub ShowTheStream(ByVal tr As TextReader)

 Dim i As Integer

 Do

i = tr.Read()

 If i >= 0 Then

Console.Write (Chr(i)) 

End If

Loop While i >= 0

 End Sub

Sub Main()

Dim sr As New StringReader(StringToReadFrom) 

Dim i As Integer 

ShowTheStream (sr)

  Console. WriteLine()

Dim fs As New FileStream("..\demo.txt", FileMode.Open)

Dim strread As New StreamReader(fs)

ShowTheStream (strread)

fs.Close()

strread.Close()

Console. ReadLine() 

End Sub

End Module

Другие классы System. I0

В пространстве имен System. I0 присутствуют и другие классы, заслуживающие внимания.

System.I0.Directory и System.IO.Directorylnfo

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

System.IO.File и System.IO.Filelnfo

Классы предназначены для выполнения различных операций с файлами, в том числе создания, удаления, копирования, перемещения и проверки существования файлов. Также с их помощью можно открывать файлы (функции открытия файлов возвращают объекты System. I0. FileStream, используемые при последующих операциях чтения и записи).

System. IO.FileSystemlnfo

Базовый класс для классов System. I0. Di rectory Info и System. I0. Filelnfo. Используется при перемещении в иерархии каталогов для получения информации о каталогах и файлах.

System.IO.FileSystemWatcher

Класс предназначен для отслеживания событий файловой системы (создания, удаления и модификации файлов).

System.IO.IsolatedStorage (пространство имен)

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

 

Сериализация и управление данными

Типичные задачи, решаемые на Visual Basic, так или иначе связаны с перемещением и хранением данных. Учитывая это обстоятельство, логично предположить, что в .NET Framework включена основательная поддержка хранения, управления и преобразования данных. В пространстве имен System.Data реализована модель ADO .NET — новейшая версия модели ADO, хорошо знакомой программистам VB. Пространства имен иерархии System.XML предназначены для работы с данными в формате XML — нового стандарта разметки данных. Наконец, пространства иерархии System.Runtime.Serialization позволяют сохранять объекты в потоках данных с последующим восстановлением.

Любые попытки описать эту тему на нескольких страницах обречены на неудачу. Сначала я хотел полностью проигнорировать проблемы сериализации, но потом выяснилось, что ей все же придется посвятить хотя бы несколько слов. Представьте, что наш экскурсионный автобус едет по длинной, скучной дороге к очередной достопримечательности, а экскурсовод тем временем рассказывает о других замечательных городах, расположенных неподалеку. Огни этих городов видны на горизонте, но их посещение откладывается на будущее.

 

Сериализация

Итак, мы хотя бы в общих чертах познакомились с потоками данных. Устрашающий термин «Сериализация» в действительно имеет вполне понятный смысл:

речь идет о сохранении состояния объекта в потоке данных. В будущем сериализованный объект восстанавливается по данным, прочитанным из потока. Объект с поддержкой сериализации должен знать, как сохранять и восстанавливать свое состояние.

Разработчики компонентов VB6 знакомы с концепцией сериализации по наборам свойств (property bags). Некоторые даже знают, что на уровне внутренних механизмов СОМ для выполнения сериализации применяются интерфейсы IPersistStream и IPersistStorage. С точки зрения программиста в VB .NET происходит нечто очень похожее, хотя на самом деле все работает совершенно иначе.

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

В приложении Serialization (листинг 12.8) определяется простой класс SerializationTest, который умеет сохранять свое состояние. Класс содержит две открытые переменные m_MyString и m_My!nteger. Чтобы наделить его возможностью сериализации, мы устанавливаем атрибут Serializable. Этот атрибут приказывает .NET организовать автоматическое сохранение всех переменных и свойств класса, не помеченных атрибутом <NonSerialized>.

Класс также содержит два метода. Первый метод сохраняет объект в потоке SOAP, а второй создает новый объект SerializationTest поданным, прочитанным из потока SOAP. Учтите, что эти методы не обязаны входить в класс — в нашем примере это сделано лишь для удобства.

Листинг 12.8. Пример сериализации

' Простой пример сериализации

1Copyright ®2001 by Desaware Inc. AU Rights Reserved

Imports System.Runtime.Serialization

<Serializable()> Public ationTest

%

Public m_MyString As String 

Public m_MyInteger As Integer

Public Function DumpToSoap(ByVal sc As serializationtest) As String

Dim ms As New System. 10.MemoryStream()

Dim sf As New Formatters.Soap.SoapFormatter()

' Сохранить класс в потоке данных

sf . Serialize(ms, sc)

ms.FlushO

' Прочитать данные из потока в строковую переменную

ms.Seek(0, 10.SeekOrigin.Begin)

Dim tr As New System.10.StreamReader(ms)

Dim res As String

res = tr.ReadToEnd()

ms.Close()

Return res 

End Function

Public Shared Function GetFromSoap(ByVal soapstring As String) As

 SerializationTest

Dim ms As New System.10.MemoryStream()

Dim sw As New System.10.StreamWriter(ms)

' Записать строку в поток данных

sw.Write (soapstring)

sw.Flush()

ms. Flush()

ms.Seek(0, 10.SeekOrigin.Begin)

Dim sc As serializationtest

' Загрузить объект по описанию Soap

Dim sf As New Formatters.Soap.SoapFormatter()

sc = CType(sf.Deserializers), serializationtest)

ms.Close()

Return (sc) 

End Function

End ont>

Module Modulel

Sub Main()

Dim st As New SerializationTest()

 st.m_My!nteger = 5 st.m_MyString = "A test string"

Dim soapstring As String

soapstring = st.DumpToSoap(st) 

console.WriteLine (soapstring)

st = serializationtest.GetFromSoap(soapstring)

console.WriteLine ("Results after object is loaded")

console.WriteLine (st.m_MyString & " " & CStr(st.m_My!nteger))

console. ReadLine()

End Sub 

End Module

В этом фрагменте сериализация выполняется в двух местах. Объект класса System.Runtime.Serialization.Formatters.Soap.SoapFormatter сохраняет объект в потоке данных произвольного типа (в нашем примере — MemoryStream), после чего содержимое потока немедленно читается в строковую переменную при помощи объекта StreamReader. В .NET Framework также имеется объект BinaryFormatter, сохраняющий объект в виде компактного двоичного потока. Создание объекта выполняется в противоположном направлении: строка записывается в поток данных, а затем восстанавливается при помощи объекта SoapFormatter. Конечно, вы можете определить собственные объекты для выполнения сериализации или реализовать в сохраняемом объекте интерфейс ISeri alizable.

Если новомодные концепции «объектов в Интернете» обошли вас стороной, на всякий случай поясню: сокращение SOAP означает «простой протокол для работы с объектами» (Simple Object Access Protocol) — стандартный способ описания объектов, их содержимого и методов на языке XML (extensible Markup Language). Сериализованная версия объекта в формате SOAP выглядит так:

<SOAP-ENV:Envelope xmlns:xsi= "http://www.w3,org/2000/10/XMLSchema-instance"

xmlns:xsd="http://www.w3.org/2000/10/XMLSchema"

xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/"

SOAP-ENV:encodingStyle=http://schemas.xmlsoap. org/soap/encoding/

xmlns:al="http://schemas.microsoft.

com /urt/NSAssem/Serialization/Serializati

on">

<SOAP-ENV:8ody>

<al:SerializationTest id="ref-1">

<m_MyString id="ref-3">A test string</m_MyString>

<m_MyInteger>5</m_My!nteger>

</al:SerializationTest>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

Протокол SOAP является текстовым и может транспортироваться при помощи протокола HTTP, что позволяет организовать передачу SOAP-данных через брандмауэры (firewalls) (при условии, что настройка брандмауэра разрешает прохождение HTTP, главного протокола World Wide Web), а благодаря наличию стандарта объекты в формате SOAP могут использоваться в любых операционных системах и средах.

Впрочем, это всего лишь теоретические рассуждения. На практике стандарт SOAP еще не утвердился и пока идут горячие споры относительно того, чьи расширения станут или не станут частью этого стандарта. Другими словами, сейчас слишком рано говорить о том, станет ли SOAP полноценным стандартом или же выродится в семейство плохо совместимых протоколов (как HTML и DHTML).

Будем надеяться на лучшее1.

1 По мнению технического редактора, из этой фразы непонятно, на какой же именно исход надеется автор. Конечно, я бы предпочел, чтобы SOAP стал общепринятым межплатформенным стандартом со стопроцентной совместимостью.

 

ADO .NET и XML

Если вы, как и большинство программистов, будете работать с базами данных в VB .NET, я рекомендую прочитать документацию Microsoft, а затем найти хорошую книгу по ADO .NET.

А пока я лишь приведу несколько фактов, которые необходимо знать об ADO .NET.

  •  Вы не обязаны переходить на ADO .NET. При желании вы можете продолжать использовать COM ADO.
  •  В ADO .NET не предусмотрено сохранение текущего состояния. Для работы с данными используется объект Data Set, содержащий копию рабочих данных. После внесения необходимых изменений эти данные записываются обратно в источник данных.
  •  ADO .NET позволяет работать с разными источниками данных, но самой интересной возможностью мне кажется работа с данными XML. Более того, ADO .NET использует XML для взаимодействия с источником данных. Это позволяет избежать проблем с безопасностью и настройкой брандмауэров, из-за которых удаленное подключение к источникам данных в СОМ считалось делом весьма творческим.

Простое применение ADO .NET продемонстрировано в приложении TVListing. Модуль TVListingDB.vb приведен в листинге 12.9. Класс TVListingDB содержит объект таблицы данных ADO .NET. В процессе динамического построения в таблицу включаются два поля: название телесериала и время показа (для простоты представленное в строковом формате). Свойства объектов DataColumn определяют поведение соответствующих полей базы данных.

Метод LoadlnitialData заполняет таблицу исходными данными. Вызовы методов Debug позволяют проследить за динамикой изменения записей. При первоначальном создании объекта Data Row и заполнении его данными эта информация еще не находится в базе. После вызова метода DataRow. Add объект DataRow считается «новым» («New»), но еще не принятым в базу. После вызова DataTable. AcceptChanges объект переходит в «неизмененное» состояние («Unchanged»). В этом состоянии он пребывает до ближайшей модификации, после чего переходит в измененное («Modified») состояние и остается в нем до подтверждения изменений. Метод GetDataSet является одним из способов получения объекта DataSet для всей таблицы. На практике объект DataSet обычно загружается в результате выполнения запроса SQL или установки фильтра для таблицы.

Листинг 12.9. Модуль TVListingDB.vb приложения TVListing

Imports System.Data

 Public gDB

 Public TVTable As New DataTable("TVListing")

Public Sub New()

Dim showcol As New DataColumn("Name", GetType(String))

 Dim showtime As New DataColumn("Time", GetType(String))

 ' Неопределенные значения недопустимы 

showcol.AllowDBNull = False 

showtime.AllowDBNull = False

TVTable.Columns.Add (showcol) 

TVTable.Columns.Add (showtime)

 End Sub

Public Sub LoadlnitialData()

 Dim newRow As DataRow

newRow = TVTable.newRow()

newRow.Item(0) = "Star Trek"

newRow.Item(l) = "13:00"

TVTable.Rows.Add (newRow)

newRow = TVTable.newRow()

newRow. ItemC'Name") = "Babylon 5"

newRow.Item("Time") = "14:00"

TVTable.Rows.Add (newRow)

newRow = TVTable.newRow()

newRow("Name") = "Battleship Galactica"

newRow("Time") = "15:00"

Debug.WriteLine("Before adding, Row is: " &_

newRow.RowState.ToString)

 TVTable.Rows.Add (newRow) 

Debug.WriteLine("Before accepting to table, Row is: " &_

newRow.RowState.ToString)

 TVTable.AcceptChanges()

Debug.WritelineC'Before modification, Row is: " & _

newRow.RowState.ToString) 

newRow("Time") = "15:30"

 Debug.WriteLineC'Before accepting changes, Row is: " & _

newRow.RowState.ToString) 

newRow.AcceptChanges() 

debug.WriteLine(TVTable.Rows.Count)

 End Sub

Public Function GetDataSet() As DataSet

Dim ds As New DataSet()

ds.Tables.Add (TVTable)

Return ds

 End Function

End

Объект DataSet может содержать данные из нескольких таблиц вместе с объектами, определяющими связи между ними.

Приведенный ниже метод cmdCreateDB_Click показывает, как создать объект TVListingDBn назначить таблицу источником данных для элемента DataGrid.

Private Sub cmdCreateDB_Click(ByVal sender As System.Object,

ByVal e As System.EventArgs) Handles cmdCreateDB.Click

 Dim tv As New TVListingDB()

tv.LoadlnitialData()

 dataGridl()DataSource = tv.TVTable 

dataGridl().PreferredColumnWidth = -1

 End Sub

Методы cmdXr1L_Click и cmdXML2_Click (листинг 12.10) демонстрируют применение методов DataSet.WriteXmlSchema и DataSet.WriteXml для вывода данных в формате XML.

Листинг 12.10. Вывод XML в проекте TVListing

Private Sub cmdXML_Click(ByVal sender As System.Object,

 ByVal e As System.EventArgs) Handles cmdXML.Click 

Dim tv As New TVListingDB() 

Dim sr As New StringWriten()

 tv.LoadlnitialData()

 txtResult() .Text = "" 

tv.GetDataSet.WriteXmlSchema (sr)

 txtResultO.Text = sr.ToString

End Sub

Private Sub cmdXML2_Click(ByVal sender As System.Object,

 ByVal e As System.EventArgs) Handles cmdXML2.Click

 Dim tv As New TVListingDB() 

Dim sr As New StringWriter()

 tv.LoadlnitialDataO txtResultO .Text = "" 

tv.GetDataSet.WriteXml (sr) 

txtResult().Text = sr.ToString

End Sub

Данные XML приведены в листинге 12.11. Одной из приятных особенностей ADO .NET является то, что при актуализации данных в источник передаются не все данные, а только изменившиеся, что способствует снижению сетевого трафика.

Листинг 12.11. Данные XML из проекта TVListing

<NewDataSet>

<xsd:schema id="NewDataSet" targetNamespace="" xmlns=""

  xmlns:xsd="http://www.w3.org/2000/10/XMLScheraa"

  xm\ns:msdata="urn:schemas-microsoft-com:xml-msdata">

 <xsd:element name="TVListing"> 

<xsd :complexType> <xsd:aU>

<xcs:element name="Name" type="xsd:string"/> 

<xcs:element name="Time" minOccurs="0" type="xsd:string"/> 

</xsd:all> </xsd:complexType> 

</xsd:element>

<xsd:element name="NewDataSet" msdata:IsDataSet="true">

 <xsd:complexType> 

<xsd:choice maxOccurs="unbounded">

<xsd:element ref="TVListing"/>

 </xsd:choice> 

</xsd:complexType> 

</xsd:element> <xsd:schema> 

<TVListing>

<Name>Star Trek</Name> 

>Time>13:00</Time>

 </TVListing> <TVListing>

<Name>Babylon 5</Name>

 >Time>14:00</Time> 

</TVListing>

 <TVListing>

<Name>BattleStar Galactica</Name>i 

>Time>15:30</Time> 

</TVListing> 

</NewDataSet>

В .NET Framework также входит пространство имен System.XML. В него входят средства лексического анализа и управления потоками данных XML.

 

Итоги

На этом завершается наше краткое знакомство с важнейшими пространствами имен. Возможно, вас интересует, о чем я не упомянул? Уверяю вас, «за кадром» осталось гораздо больше классов, чем упоминается в тексте. В частности, совершенно не рассматривалась область разработки компонентов. В .NET Framework можно найти классы для чего угодно, от управления ресурсами до лексического анализа файлов в формате HTML и XML.

Впрочем, вы получили представление о важнейших пространствах имен, играющих ключевую роль в решении многих задач программирования — и, в первую очередь, связанных с возможностями VB6, не поддерживаемых напрямую в Visual Basic .NET. Мы рассмотрели основные системные объекты и принципы их взаимодействия. Вы познакомились с коллекциями, вводом-выводом, графическим выводом и печатью, основными принципами использования этих пространств имен и отличиями от решений, использованных в Visual Basic 6. Глава завершается кратким введением в сериализацию и ADO .NET (каждая из этих тем заслуживает отдельной книги).

Напоминаю, что многие классы пространства имен System небезопасны по отношению к потокам. При использовании этих классов в многопоточных приложениях необходимо действовать очень осторожно (то есть не предоставлять общий доступ к объектам .NET Framework со стороны нескольких программных потоков, если в документации не сказано, что эти классы обладают потоковой безопасностью).

Наша короткая экскурсия подошла к концу, но знакомство с миром VB .NET еще не закончено. В следующих трех главах мы рассмотрим пространство имен System. Windows . Forms, используемое для создания приложений Windows, и ряд пространств имен из области сетевых операций и средств взаимодействия СОМ. Уровень изложения не претендует на сколько-нибудь достаточную глубину, но я надеюсь, что приведенный обзор поможет вам правильно взяться за самостоятельное изучение.

Назад   Вперёд

 


Инфо
Сайт создан: 20 июня 2015 г.
Рейтинг@Mail.ru
Главная страница