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

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

Синтаксис

От изменений в базовых типах данных и операторах Visual Basic мы переходим к изменениям в самом языке. В этой главе рассматриваются изменения, не связанные с объектами и объектно-ориентированным программированием (ибо последние заслуживают отдельной главы).

Не стоит рассматривать эту главу как подробный справочник по всем изменениям в языке — эта информация приведена в электронной документации Visual Studio .NET. Прежде чем читать дальше, просмотрите раздел «What's New in Visual Basic». Как всегда, я лишь описываю эти изменения в практическом контексте и рассматриваю их возможное влияние на программный код.

 

Вызовы функций и параметры

В Visual Basic .NET в области вызова функций произошли определенные изменения. Одни сразу бросаются в глаза, другие менее заметны, но все равно важны.

Рациональный механизм вызова

В VB6 существует четкое разделение между функциями и процедурами (subroutines). Функция всегда возвращает некоторое значение или 0, если возвращаемое значение не указано. Процедуры никогда не возвращают значений. Синтаксис вызова функций и процедур выглядит так:

' Функции

х = F(Y) ' Результат используется

F(Y) ' Результат не используется

Call F(Y) ' Результат не используется

F(Y) ' Передавать Y по значению

' Процедуры

F Y

Call F(Y)

F(Y) ' Передавать Y по знамению

Наличие двух вариантов синтаксиса (с круглыми скобками и без) само по себе неприятно, но дело обстоит еще хуже. Допустим, у вас имеется функция F. Чем отличаются следующие два вызова:

х = F(Y)

  F(Y)

В одном случае результат используется, а в другом — нет. Но это еще не все! Если параметр Y функции F был объявлен с ключевым словом ByRef, при первом вызове он будет передан по ссылке, а при втором — по значению. Это может привести к неприметным и неожиданным ошибкам.

В VB .NET при вызове функций и процедур используется следующий синтаксис:

х = F(Y) ' Результат используется

Call F(Y) ' Результат не используется

F Y ' Результат не используется

F() ' Вызов функции без параметров

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

Круглые скобки, как и прежде, могут использоваться для передачи параметров по значению. В этом случае синтаксис вызова выглядит так: F((Y)) ' Принудительная передача по значению

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

 

Возвращение значений

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

Function F(ByRef As Integer) As Integer

Return (6)

Return 6

F = 6 

End Function

Команда Return позаимствована из языков C++ и С#. Вероятно, это было сделано для удобства программистов, работающих на нескольких языках. Лично мне команда Return нравится гораздо больше, чем присваивание значения имени функции. Она упрощает чтение программы и снижает вероятность ошибок при возвращении результата — особенно если ваша функция имеет несколько точек выхода.

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

 

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

В VB .NET по умолчанию параметры передаются не по ссылке, а по значению. Честно говоря, я никогда не понимал, почему в Visual Basic по умолчанию использовался механизм передачи по ссылке. Правда, в VB6 передача ByRef работает эффективнее, но с точки зрения программиста она увеличивает риск внесения ошибок при непреднамеренной модификации параметров в функции. В VB .NET передача параметров по ссылке уже не обеспечивает сколько-нибудь заметного выигрыша по быстродействию. Причины объясняются в следующем разделе.

 

Новый механизм передачи по значению

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

Единственное исключение из этих правил VB6 составляли строки в командах Deиспользуемые для передачи функциям API строк, завершенных нуль-символами) и объекты.

Давайте посмотрим) что происходит при передаче объекта в качестве параметра функции. Дальнейшее описание относится как к VB6, так и к VB .NET1.

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

На рис. 9.1 показано, как объектные параметры передаются по ссылке. Функция получает указатель на объектную переменную.

Рис. 9.1. Передача объектного параметра по ссылке

На рис. 9.2 показано, что происходит при передаче параметра по значению. Функция получает объектную переменную, содержащую дополнительную копию адреса объекта.

Рис. 9.2. Передача объектного параметра по значению

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

Приложение ObjectParams (листинг 9.1) демонстрирует происходящее на примере простого объекта с одним открытым свойством. В приложении объявляются две функции с передачей параметров по ссылке и две функции с передачей по значению. В каждой паре одна функция просто изменяет свойство X, а другая присваивает параметр новому экземпляру класса MyObject.

Листинг 9.1. Передача объектов1

Module Modulel

Public x As Integer 

End

Public >Sub FObjByRefKByRef Y As MyObject)

Y. x = 5

 End Sub

Public Sub FObjByRef2(ByRef Y As MyObject)

Y = New MyObject()

Y.x = 5 End Sub

Public Sub FObjByValKByVal Y As MyObject)

Y.x = 5 

End Sub

Public Sub FObjByVal2(ByVal Y As MyObject)

Y = New MyObject()

Y.x = 5

 End Sub

Public Sub ObjectTests() 

Dim A As New MyObject() 

Dim В As MyObject = A 

A.x = 1

Console.WriteLine ("Initial state")

Console. Wri tel_ine("Are A and В the same? " + (A Is B) .ToString()) Console.Wri teLine ("A.x: " + A. x .ToStringO + " B.x " + _

B.x.ToStringO) FObjByRefl (B)

Console.WriteLine ("After FobjByRefl")

Console.Wri teLineC'Are A and В the same? " + (A Is B) .ToString()) Console .Wri teLine ("A.x: " + A. x .ToStringO + " B.x " + _

B.x.ToStringO) A.x = 1 

В = А

FObjByRef2 (B) Console.WriteLine ("After FobjByRef2") 

Console. WriteLine("Are A and В the same? " + (A Is B) .ToString()) Console .Wri teLine ("A.x: " + A.x.ToStringO + " B.x " + _

B.x.ToStringO) A.x = 1 В = А

FObjByVaU (B)

Console.WriteLine ("After FobjByVall")

Console.WriteLine("Are A and В the same? " + (A Is B) .ToString()) Console.Wri teLine ("A.x: " + A. x .ToStringO + " B.x " + _

B.x.ToString()) 

A.x = 1 

В = А

FObjByVal2 (B)

Console.WriteLine ("After FobjByVal2")

Console.Wri teLineC'Are A and В the same? " + (A Is B) .ToString()) Console.Wri teLine ("A.x: " + A. x .ToStringO + " B.x " + _

B.x.ToString())

End Sub

1 Все исходные тексты можно найти на сайте издательства «Питер» www.piter.com. — Примеч. ред.

Результат выглядит следующим образом:

Initial state

Are A and В the same? True

A.x: 1 B.x 1

After FobjByRefl

Are A and В the same? True

A.x: 5 B.x 5

After FobjByRef2

Are A and В the same? False

A.x: 1 B.x 5

After FobjByVall

Are A and В the same? True

A.x: 5 B.x 5

After FobjByVal2

Are A and В the same? True

A.x: 1 B.x 1 *

Сначала свойство X инициализируется значением 1, а переменным В и А присваиваются ссылки на один и тот же объект.

После вызова FobjByRef1 свойство, как и следовало ожидать, становится равным 5. При этом А и В продолжают ссылаться на один объект, поскольку параметр Y не изменился.

При вызове FobjByRef 2 параметру Y присваивается новый объект. Поскольку параметр передавался по ссылке, в переменную В заносится ссылка на новый объект. Модификация свойства X относится только к новому объекту, поэтому А.Х сохраняет первоначальное значение 1.

Вызов Fobj ByVal 1 демонстрирует один важный факт: хотя объект передается по значению, значение свойства объекта изменилось.

С вызовом FobjByVal2 дело обстоит несколько сложнее. Функция создает новый объект, присваивает ссылку на него параметру Y и модифицирует свойство X. Тем не менее этот новый объект присваивается временной переменной, а не ссылке на переменную, переданной при вызове. Модификация свойства X относится к этому новому объекту. При возвращении из Fob] ByVal2 переменная В ос-

тается в прежнем состоянии. Она продолжает ссылаться на исходный объект (на который также ссылается переменная А), свойство X которого не изменялось. Что же происходит с объектом, созданным функцией? При возврате из функции FobjByVal2 временная переменная Y выходит из области видимости и перестает ссылаться на созданный объект. Объект, на который не осталось ни одной ссылки, будет уничтожен при следующей сборке мусора.

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

Однако в VB .NET механизмы передачи параметров играют значительно более важную роль и в них необходимо хорошо разбираться. Почему? Потому что в VB .NET каждая переменная является объектом.

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

Объекты структурных типов (числовые переменные, структуры и другие объекты, производные от класса System.ValueType) работают именно так, как следует ожидать. При передаче их с ключевым словом ByVal передается копия объекта структурного типа.

Передача параметров структурного типа

В программе ObjectParams (см. листинг 9.1) продемонстрирована также передача параметров структурного типа. Фрагмент, приведенный в листинге 9.2, практически идентичен фрагменту объектного типа, однако результат оказывается совершенно иным.

Листинг 9.2. Передача структурных параметров

Structure MyStruct

Public x As Integer

 End Structure

Public Sub FStructByRefKByRef Y As MyStruct)

Y.x = 5

 End Sub

Public Sub FStructByRef2(ByRef Y As MyStruct)

Y = New MyStruct()

Y.x = 5

 End Sub

Public Sub FStructByVall(ByVal Y As MyStruct)

Y.x = 5 

End Sub

Public Sub FStructByVal2(ByVal Y As MyStruct)

Y = New MyStruct()

Y.x = 5

 End Sub

Public Sub StructTests() Dim A As MyStruct Dim В As MyStruct

 А.х = 1

В = А

Console.WriteLine ("Initial state StructTests")

Console. WriteLine("Are A and В the same? " + (A. EquaU(B)) .ToString())

Console.Wri teLine ("A.x: " + A. x .ToStringO + " B.x " + _

B.x.ToString())

 FStructByRefl (B)

Console.WriteLine ("After FStructByRefl")

 Console.WriteLine("Are A and В the same? " +

(A.Equals(B)) .ToString()) 

Console.Wri teLine ("A.x: " + A. x.ToStringO + " B.x " + _

B.x.ToStringO) A.x = 1 В = А

FStructByRef2 (B)

Console.WriteLine ("After FS.tructByRef2")

Console.WriteLineC'Are A and В the same? " + (A. Equals (B)) .ToString() Console.Wri teLine ("A.x: " + A.x .ToString() + " B.x " + B. x .ToString()) A.x = 1 В = А

FStructByVall (B)

Console.WriteLine ("After FStructByVall")

Console.WriteLine("Are A and В the same? " + (A. Equals(B)) .ToString()) Console.Wri teLine ("A.x: " + A. x .ToString() + " B.x " + B. x .ToStri ng()) A.x = 1 В = А

FStructByVaU (B)

Console.WriteLine ("After FStructByVal2")

Console.WriteLine("Are A and В the same? " + (A. Equals (B)) .ToString()) Console.Wri teLine ("A.x: " + A.x .ToString() + " B.x " + B. x .ToString())

End Sub

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

При сравнении структур используется метод Equals. Это объясняется тем, что VB не разрешает переопределять оператор = для пользовательских структур, но метод Equals при желании можно переопределить, В нашем примере используется стандартная реализация метода Equals, которая осуществляет поверхностное сравнение, то есть просто сравнивает значения соответствующих полей структур.

Для структурных параметров результат выглядит следующим образом:

Initial state StructTests

Are A and В the same? True

A.x: 1 B.x 1

After FStructByRefl

Are A and В the same? False

A.x: 1 B.x 5

After FStructByRef2

Are A and В the same? False

A.x: 1 B.x 5

After FStructByVall

Are A and В the same? True

A.x: 1 B.x 1

After FStructByVal2

Are A and В the same? True

A.x: 1 B.x 1

Как видно из приведенных результатов, изменилось само понятие равенства двух переменных. В нашем примере переменные считаются равными лишь в том случае, если совпадают их свойства X.

В результате вызова FStructByRef1 свойство X объекта В становится равным 5, поэтому при выходе из функции А и В перестают быть равными. Вызов FStructByRef2 приводит к тому же результату, но по другим причинам. Параметру Y присваивается новая структура (что приводит к модификации переменной В), а ее свойство X становится равным 5.

В двух последних функциях переменная В остается неизменной. Это доказывает, что параметру Y действительно была присвоена копия всей структуры.

По этому принципу работают все структурные типы, включая Boolean, Byte, Char, Decimal, Double, Enum, Single, Integer, Short и Long. Для них ключевые слова ByVal и ByRef интерпретируются именно так, как подсказывает здравый смысл.

Однако в приведенном списке отсутствуют два важных случая: строковые переменные и массивы.

Передача строковых параметров

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

Листинг 9.3. Передача строковых параметров

Public Sub FStringByRefl(ByRef Y As String)

Mid$(Y, 1, 1) = "A"

 End Sub

Public Sub FStringByRef2(ByRef Y As String)

Y = "Hello" 

End Sub

Public Sub FStringByRef3(ByRef Y As String)

Mid$(Y, 1, 1) = Mid$(Y, 1, 1)

 End Sub

Public Sub FStringByValKByVal Y As String)

Mid$(Y, 1, 1) = "A"

 End Sub

Public Sub FStringByVal2(ByVal Y As String)

Y = "Hello" 

End Sub

Public Sub StringTests()

Dim A As String = "Hello"

Dim В As String

В = А

Console.WriteLine (Chr(10) + "Initial state StringTests")

Console.WriteLine("Are A and В the same? " + (A Is B) .ToString())

Console.WriteLine ("A: " + A + " B: " + B)

FStringByRefl (B)

Console.WriteLine ("After FStringByRefl")

Console.WriteLine("Are A and В the same? " + (A Is B) .ToString())

Console.WriteLine ("A: " + A + " B: " + B)

A = "Hello"

В = А

FStringByRef2 (B)

Console.WriteLine ("After FStringByRef2")

Console.WriteLine("Are A and В the same? " + (A Is B) .ToString())

Console.WriteLine ("A: " + A + " B: " + B)

A = "Hello"

В = А

FStringByRefB (B)

Console.WriteLine ("After FStringByRef3")

Console.WriteLineC'Are A and В the same? " + (A Is B) .ToString())

Console. WriteLine ("Are A and В equal? " + (A = B) .ToString())

Console.WriteLine ("A: " + A + " B: " + B)

A = "Hello"

В = А

FStringByVall (B)

Console.WriteLine ("After FStringByVall")

Console.WriteLineC'Are A and В the same? " + (A Is B) .ToString())

Console.WriteLine ("A: " + A + " B: " + B)

A = "Hello"

В = А

FStringByVal2 (B)

Console.WriteLine ("After FStringByVal2")

Console.WriteLineC'Are A and В the same? " + (A Is B) .ToString())

Console.WriteLine ("A: " + A + " B: " + B)

 End Sub

Hе забывая о том, что строки являются объектами ссылочного типа, давайте проанализируем результат строку за строкой:

Initial state StringTests

 Are A and В the same? True 

A: Hello B: Hello

В исходном состоянии переменные А и В ссылаются на один и тот же объект. Иначе говоря, существует всего один объект String, содержащий текст «Hello». Переменные А и В относятся к одному объекту (об этом свидетельствует результат True оператора Is). Содержимое А и В тоже считается равным, что вполне очевидно, поскольку переменные ссылаются на один объект:

After FStringByRefl

Are A and B the same? False

A: Hello B: Aello

Функция FStringByRefl заменяет символ строки командой Mid$

Mid$(Y,l,l) = "A"

Задумайтесь: параметр Y, переданный по ссылке, относится к тому же объекту, что и А. Было бы логично предположить, что модификация символа этой строки приведет к модификации строки А. В приложении ObjectParams изменение свойства X переменной Y приводило к модификации свойства X переменной А, ссылавшейся на тот же объект.

На самом деле функция Мid$ модифицирует не тот объект String, на который ссылается Y. Она создает новую строку с другим первым символом и присваивает ее параметру Y, что приводит к модификации переменной В.

Почему это происходит? Потому что строки VB .NET являются неизменными (см. главу 8). Любая операция, которая на первый взгляд модифицирует строку, в действительности создает новый экземпляр строки. Таким образом, хотя переменная В была изменена и теперь ссылается на новую строку, переменная А продолжает ссылаться на исходную строку «Hello»:

After FStringByRef2

Are A and В the same? True

A: Hello B: Hello

Функция FStringByRef 2 присваивает Y новую строку, совпадающую с исходной строковой величиной: 

Y = "Hello"

Интуиция подсказывает, что В будет ссылаться на новый объект String со строкой «Hello». Но на практике выясняется, что В и А по-прежнему ссылаются на один и тот же объект! Дело в том, что компилятор VB .NET старается разумно подходить к работе со строковыми литералами. Он видит, что Y присваивается строка «Hello», уже присутствующая во внутренней таблице строк, поэтому компилятор просто присваивает Y ссылку на тот же строковый литерал. Эта возможность основана на принципе неизменности строк: CLR знает, что литерал ни при каких условиях не изменится, поэтому там, где это возможно, строковым переменным присваиваются уже существующие литералы.

Пример FStringByRef3 еще нагляднее поясняет сказанное. На первый взгляд эта строка вообще не изменяется:

 Mid$(Y, 1, 1) = Mid$(Y, I, 1)

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

After FStringByRefS 

Are A and В the same? False

 Are A and В equal? True 

A: Hello B: Hello

Другими словами, А и В ссылаются на разные переменные с одинаковым содержимым. Почему? Хотя содержимое строки не изменяется, CLR об этом не знает. В соответствии с принципом неизменности строк переменная Y должна быть связана с новой строкой, даже если ее содержимое будет таким же, как прежде.

Для пары функций с передачей по значению выводятся следующие результаты:

After FStringByVall

Are A and B the same? True

A: Hello B: Hello

After FStringByVal2

Are A and В the same? True

A: Hello B: Hello

Именно такой результат был бы получен, если бы тип String был структурным, так как изменения не отражаются на переменной, переданной при вызове (в отличие от примера с параметрами-объектами, где изменение свойства X параметра, переданного с ключевым словом ByVal, отражалось в свойстве X исходного объекта). Такое поведение обусловлено принципом неизменности строк. Изменения в содержимом строки не могут отражаться в исходной переменной, поскольку содержимое строк вообще никогда не изменяется. Вместо этого создается новая строка, присваиваемая параметру Y. Так как параметр Y передавался по значению, .его изменения не распространяются на исходную переменную В.

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

Передача массивов

Массивы являются объектами. На массивы, в отличие от строк, не распространяется требование неизменности. Это означает, что для массивов должны выводиться те же результаты, которые были получены раньше для параметров-объектов. Изменения элементов массива внутри функции всегда должны распространяться на исходный объект, даже если массив передавался по значению (по аналогии со свойством X класса MyObject). Присваивание параметру Y нового объекта должно распространяться на исходную переменную лишь в том случае, если массив был передан по ссылке.

Я опускаю программный код для экономии места (вы найдете его в приложении ObjectParams). Результат, приведенный в листинге 9.4, выглядит именно так, как мы предполагали.

Листинг 9.4. Передача параметров-массивов

Initial state ArrayTests

Are A and В the same? True

Array A:

1, 2, 3, 4, 5

Array B:

1, 2, 3, 4, 5

After FArrayByRefl

Are A and В the same? True

Array A:

5, 2, 3, 4, 5

Array B:

5, 2, 3, 4, 5

After FArrayByRef2

Are A and В the same? False

Array A:

1. 2, 3, 4, 5

Array B:

2. 3, 4, 5, 6

After FArrayByVall

Are A and В the same? True

Array A:

5. 2, 3. 4, 5

Array B:

5, 2, 3. 4, 5

After FArrayByVal2

Are A and В the same? True

Array A:

1. 2, 3, 4, 5

Array B:

1, 2, 3, 4, 5

Подведем итог всему, о чем говорилось выше.

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

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

3. Массивы являются объектами и подчиняются правилам, представленным в пункте 2.

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

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

Необязательные параметры и значения по умолчанию

Visual Basic .NET требует, чтобы для необязательных параметров задавались значения по умолчанию (в VB6 значения по умолчанию могли отсутствовать, в этом случае пропущенным параметрам присваивался 0 или пустая строка в зависимости от типа переменной).

Поскольку в Visual Basic .NET отсутствует универсальный тип Variant, естественно, в нем не предусмотрена поддержка необязательных параметров Variant и не поддерживается функция IsMissing для их проверки.

ParamArray

Ключевое слово ParamArray позволяет вызывать функцию с переменным количеством параметров. В VB6 реализация массива параметров выглядела примерно так:

Public Function A(ParamArray V() As Variant)

 Dim x As Integer

 If IsMissing(X) Then

Debug.Print "V is missing"

 Else

For x = 0 To UBound(V())

Debug.Print V(x)

 Next x

 End If 

End Function

Параметры Variant (если они присутствуют) передаются по ссылке. Прежде чем перебирать содержимое массива, приходится убеждаться в его наличии при помощи функции IsMissing.

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

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

Public Sub ParamArrayTestl(ParamArray ByVal A() As Integer)

Dim x As Integer

For x = 0 To UBound(A) 

Console.WriteLine(A(x))

Next

 End Function

Функция UBound возвращает -1 для массива нулевой длины, что упрощает программный код, необходимый для перебора элементов массива.

Правила видимости

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

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

Рассмотрим следующий пример из приложения ScopingVBG.

Sub Main()

Dim Counter As Integer

' X = 3 ' В этой точке переменная не определена

 For Counter = 1 То 3

If True Then ' Всегда входить в этот блок 

Dim X As Integer

 Debug.Print X 

X = Counter 

End If

Next Counter

Debug.Print "Outside of block: " & X

 End Sub

Выходные данные в окне отладки выглядят так:

1

2

Outside of block: 3

Программа демонстрирует ряд ключевых моментов, относящихся к видимости переменных в VB6.

 Рассмотрим эквивалентный код VB .NET из проекта Scoping.

Module ScopingMod

Sub Main()

Dim Counter As Short

1 X = 3 ' В этой точке переменная не определена

For Counter = 1 То 3

If True Then ' Всегда входить в этот блок

 Dim X As Short

 Console.WriteLine (X) 

X = Counter

 End If

Next Counter

'Console.WriteLineC'Outside of block: " & X) 

Console.ReadLine()

 End Sub

 End Module

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

0

1

2

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

Просто для сравнения давайте посмотрим, как подобные ситуации выглядят в новом языке С#. В листинге 9.5 приведен фрагмент примера ScopingCSharp.

Листинг 9.5. Пример ScopingCSharp

static void Main(string[] args) 

{

short counter; 

//short x;

//x =50; // В этой точке переменная не определена for(counter = l;counter<=3;counter++) 

{

if (true) 

{

short x=0; // В С# необходима инициализация

 Console.WriteLine(x);

 x= counter; 

}

//Console.WriteLine("Outside of block: " + x.ToString());

  Console. ReadLine() ; 

}

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

0

0

0

Продолжительность жизни х, как и прежде, определяется продолжительностью работы функции. Тем не менее, язык С# требует, чтобы переменные инициализировались перед использованием, и выполняет инициализацию в точке объявления. Возможно, это чуть снижает быстродействие, но программа становится более предсказуемой (хотя подобные ошибки обычно исправляются на стадии начальной разработки).

В листинге 9.6 приведен интересный фрагмент на языке C++ из приложения ScopingCPP.

Листинг 9.6. Приложение ScopingCPP

int main(void) 

{

short counter; 

short x;

x = 50; // В этой точке переменная не определена 

for(counter = l;counter<=3;counter++) {

 if (true) {

short x; // В C++ инициализация не обязательна

  Console::WriteLine(x);

 x= counter; 

}

Console: :Wri teLine(x .ToString() ) ; 

Console: : ReadLine() ;

return 0; 

}

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

0

1

2

50

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

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

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

Откровенно говоря, мне не хватает этой особенности C++, даже несмотря на то, что программы, написанные по правилам C#/VB .NET, легче читаются1.

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

Статические переменные

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

В VB6 вы могли объявить статическую функцию, все переменные которой по умолчанию были статическими. За долгие годы программирования на Visual Basic я ни разу не использовал статические функции в своих программах, поэтому не могу сказать, что меня огорчит их утрата.

Статические переменные существуют на уровне экземпляров класса. Рассмотрим приложение Statics (листинг 9.7).

Листинг 9.7. Приложение Statics

Public Shared Sub SharedTest()

Static x As Integer

x = x + 1

console.WriteLine (x) 

End Sub

Public Sub Test()

 Static x As Integer

 x = x + 1

console.WriteLine (x)

End Sub 

End

Module Modulel

Sub Main()

 Dim cl As New C() 

Dim c2 As New C() 

cl.Test() 

cl.Test() 

c2.Test() 

c2.Test()

 c.SharedTest() 

c.SharedTest()

 console.ReadLine()

End Sub 

End Module

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

1

2

1

2

1

2

Переменные cl и с2 относятся к разным экземплярам класса С. Из приведенных результатов видно, что метод Test каждого экземпляра, как и метод SharedTest1, обладает собственной копией статических переменных.

Замечание: при знакомстве с платформой .NET неизбежно возникает путаница между статическими (static) и общими (shared) переменными. Причина заключается в том, что ключевое слово statics C++ и С# используется для определения переменных, которые являются как общими, так и статическими. В документации и статьях, посвященных .NET, авторы нередко не учитывают двойственной трактовки этих терминов.

Следующие простые правила помогут вам избежать недоразумений.

  •  Общими переменными в VB .NET называются переменные, принадлежащие всем экземплярам класса.
  •  Общие методы в VB .NET не связываются с конкретными экземплярами классов (см. главу 10).
  •  Статические локальные переменные видимы только внутри метода или функции, в которых они были определены, но существуют на протяжении всего жизненного цикла программы
  •  Методы и переменные, ранее называвшиеся «статическими», в терминологии VB .NET называются «общими».

1 Общие методы и переменные рассматриваются в главе 10.

Обработка ошибок

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

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

Откровенно говоря, в C++ она ничуть не лучше.

Вероятно, сейчас программисты VB6 удивляются, что же я имею против старой доброй команды On Error Goto, а программисты C++ решили, что автор совсем выжил из ума, ведь в C++ предусмотрена Структурированная обработка ошибок, о которой я собираюсь поведать.

Не торопитесь негодовать. У меня есть веские причины для подобных заявлений.

История

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

  •  при обращении к методам или свойствам объектов СОМ;
  •  при вызове функций API;
  •  при выполнении операций языка VB6 (например, числовое переполнение).

Давайте рассмотрим все эти причины.

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

В механизме обработки ошибок СОМ используется 32-разрядный код результата HRESULT. Сейчас мы не будем останавливаться на HRESULT (подробное описание можно найти в MSDN). Достаточно сказать, что HRESULT обеспечивают классификацию ошибок по категориям (элементы ActiveX, Automation, системные ошибки) и числовым кодам. При вызовах методов СОМ, при обращениям к свойствам и вызове практически всех функций подсистемы OLE возвращаются коды HRESULT, которые могут интерпретироваться стандартным образом. Кроме того, объект может дополнительно реализовать интерфейс ISupportErrorlnfo, при помощи которого компонент передает клиенту дополнительную информацию об ошибке (текстовые сообщения, данные об источнике и даже путь к справочному файлу с описанием ошибки).

Ошибки, происходящие в функциях API, описываются возвращаемым значением функции. Для каждой функции определяются собственные коды ошибок. Одни функции в случае ошибки возвращают -1, другие возвращают 0, третьи — величину, отличную от 0. Обычно в программе можно получить дополнительную информацию об ошибке при помощи функции API GetLastError, которая использует список стандартных ошибок из заголовочного файла winerror.h, входящего в поставку Windows Platform SDK (и Visual C++). Непоследовательность в возвращении кодов ошибок — характерная черта необдуманной, хаотичной эволюции Windows API. Значения, возвращаемые GetLastError, были определены в процессе перехода Windows от 16-разрядных версий к 32-разрядным. В частности, одна из категорий кодов HRESULT соответствует стандартным кодам ошибок API.

В процессе языковых операций могут возникать ошибки времени выполнения (выход за границы массива, числовое переполнение и т. д.), обрабатываемые в VB6 командой On Error Goto. Поскольку VB6 маскирует от программиста вызовы методов СОМ и обращения к свойствам, ошибки в компонентах СОМ также могут приводить к возникновению ошибок времени выполнения. В этом случае возвращаемый код HRESULT преобразуется в ошибку VB времени выполнения.

Обработка ошибок в VB6

В Visual Basic ошибки перехватываются командой On Error Goto. Чем плохо подобное решение?

  •  В каждой функции или процедуре может действовать лишь один активный обработчик ошибок. Вы можете перехватывать некоторые ошибки и передавать остальные вызывающей функции, однако «вложенные» обработчики ошибок такого рода определяются на уровне функции, а не в блоках внутри нее.
  •  Нетривиальная обработка ошибок приводит к частой передаче управления, за которой трудно проследить при чтении программы.
  •  Команда On Error Resume Next решает проблему запутанной передачи у правления, однако программа при этом загромождается, поскольку встроенную проверку ошибок приходится включать везде, где они могут произойти.
  •  Непоследовательность получения данных об источнике ошибки (особенно при работе с функциями API) часто порождает общую непоследовательность обработки ошибок в VB. Нередко встречаются программы, в которых одни функции возвращают коды ошибок, а другие инициируют ошибки методом Raise.

Обработка ошибок в VC++

Программисты Visual C++ полагают, что обработка ошибок — одна из областей, доказывающих превосходство C++ над Visual Basic. Дело в том, что в Visual C++ поддерживается механизм структурной обработки ошибок, значительно превосходящий синтаксическую конструкцию VB On Error Goto. Вскоре мы поговорим о структурной обработке ошибок, а пока я скажу, почему программисты C++ ошибаются.

Да, в VC++ поддерживается структурная обработка ошибок, и она действительно лучше конструкции On Error Goto. К сожалению, в VC++ 6.0 этот механизм обработки ошибок очень плохо поддерживается на уровне Windows.

  •  Функции API, используемые в VC++ значительно чаще, чем в VB, по-прежнему возвращают коды ошибок вместо инициирования ошибок, перехватываемых механизмом структурной обработки.
  •  Ошибки методов СОМ и обращений к свойствам, которые VB6 автоматически преобразует в ошибки времени выполнения, для программистов C++ просто возвращаются в виде HRESULT. Для этих ошибок механизм структурной обработки тоже не подходит.
  •  Большинство ошибок исполнительной среды и библиотек C++ не перехватывается механизмом структурной обработки. Обычно эти ошибки вообще не обрабатываются и инициируют исключения защиты памяти. В лучшем случае это приводит к запуску отладчика, в худшем — к аварийному завершению приложения (а в Windows 95/98/ME возможно «зависание» системы).

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

Структурная обработка ошибок

Что такое «структурная обработка ошибок»? В этом термине главным словом является слово «структурная». Visual Basic, C# и C++ принадлежат к классу языков, имеющих блочную структуру. В таких языках программист определяет блоки программного кода, выполняемые как единое целое. Иначе говоря, программист может использовать конструкции вида «если некоторое условие, выполнить блок; в противном случае выполнить другой блок». При этом каждый блок может иметь произвольный размер и содержать другие вложенные блоки.

В С# и C++ границы блоков задаются символами { и }. В Visual Basic блоки ограничиваются синтаксическими элементами самого языка. Мы сейчас говорим не о конкретном синтаксисе, а о возможности группировки команд в блоки, выполняемые на основании единожды принятого решения, без необходимости принимать это решение для каждой строки внутри блока.

Структурная обработка ошибок просто расширяет этот принцип в области обработки ошибок.

Обобщенный синтаксис структурной обработки ошибок выглядит следующим образом:

Try

блок Catch тип ошибки

блок, выполняемый при возникновении ошибки 

Catch другой тип ошибки

блок, выполняемый при возникновении ошибки

Finally

блок, выполняемый перед выходом из блока Try 

End Try

Внутри каждого блока могут находиться другие блоки, в том числе и вложенные блоки Try...End Try!

Различные типы ошибок представляются объектами исключений (exceptions), производными от класса System. Exception. Блок Catch также может содержать секцию When с указанием дополнительного условия. Блок Catch выполняется только при возникновении ошибки определенного типа и при истинности условия, заданного в секции When.

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

Безусловно, синтаксическая конструкция Try...End Try удобна, но по-настоящему фантастически выглядит ее полная интеграция с исполнительной средой, начиная с самого нижнего уровня. Более того, все ошибки, возникающие в используемых объектах СОМ, средствами .NET тоже отображаются на исключения!

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

Проект Error-Handling: общие сведения

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

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

Листинг 9.8. Описание проекта ErrorHandling на псевдокоде

Sub Main 

Try 

Try

Открыть файл 

Catch-файл не найден

Создать файл и записать данные 

Catch-остальные ошибки

Выйти из программы 

End Try

Try

Прочитать строку из файла 

Try

Вывести 100/числовое значение строки 

Catch-строка не преобразуется в число

Выполнить обработку ошибок 

Catch-деление на ноль

Выполнить обработку ошибок 

Catch-любые другие ошибки

Перевести на следующий уровень с дополнительной информацией

 End Try

Catch-любая ошибка

Выполнить обработку ошибок

 Finally

Закрыть файл и удалить его 

End Try 

Finally

Console.ReadLine

 End Try

 End Sub

Функция Main начинается с объявления блока Try. Возникает мысль, что мы пытаемся перехватывать все ошибки, возникающие в программе. И правда, это является веской причиной для заключения всей программы в блок Try. У вас появляется возможность самостоятельно обработать ошибки времени выполнения, не полагаясь на стандартные средства .NET Framework. Например, сообщение об ошибке .NET можно сопроводить контактной информацией (адресом электронной почты или телефоном). Возможны и более радикальные варианты, скажем, обработчик строит отчет с полным содержимым стека и отправляет его в вашу службу технической поддержки по электронной почте!

Однако в данном случае вся программа заключена в блок Try по совершенно иным соображениям. Возможно, вы заметили, что большинство консольных приложений в этой книге завершается строкой Console. Readline. Причина очевидна: при наличии этой команды окно остается открытым до того момента, когда пользователь нажмет клавишу Enter, что позволяет просмотреть результаты ее работы. Однако в проекте ErrorHandling некоторые перехваченные ошибки требуют немедленного завершения программы, для чего проще всего воспользоваться командой Exit Sub. Но как вы увидите результат, если выход из функции Main приводит к немедленному закрытию окна вывода (поскольку команда Console.ReadLine при этом не выполняется)?

Проблема решается заключением всей программы в блок Try с размещением итоговой команды Console.ReadLine в блоке Finally.

Блок Finаllу гарантировано будет выполнен перед выходом из блока Try. Он обладает более высоким приоритетом по сравнению с командами Exit Sub и Exit Function. Другими словами, даже при вызове Exi t Sub и Exit Functions блоке Try...End Try все итоговые блоки Finally будут выполнены перед выходом из программы.

Перед нами не только принципиальные изменения в смысле команд Exit Sub и Exit Function, но и принципиальное улучшение, поскольку величайший недостаток команд Exit Sub и Exit Function заключался именно в том, что они затрудняли деинициализацию и завершающие действия. В VB6 это либо приходилось делать при каждом вызове Exit Sub, либо выполнять безусловный Goto-переход1 в общий блок, завершаемый вызовом Exit Sub и Exit Function.

1 Конечно, команда Goto вредна. Тем не менее централизованное выполнение завершающих действий перед выходом из функции — одна из тех областей VB6, где Goto оказывается меньшим злом по сравнению с размещением завершающего кода в каждой точке выхода. Другая область VB6, в которой приходится мириться с командой Goto, — это, конечно, конструкция On Error Goto, поскольку у вас просто нет другого выбора. В VB .NET оба этих случая уже не актуальны, поэтому команда Goto еще вреднее, чем в VB6.

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

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

Проект ErrorHandling: подробный анализ кода

Перейдем к рассмотрению кода приложения ErrorHandling.

В настоящем примере для демонстрации разных режимов оповещения об ошибках используются две логические константы. Если константа ShowErrors равна True, при обнаружении в файле недопустимых чисел на консоль выводится сообщение — либо о том, что строку не удается преобразовать в целое число, либо о том, что прочитанное число (например, ноль) приводит к ошибке деления. Если константа ThrowOnBadFormat равна True, при обнаружении строки, не преобразуемой в целое число, программа инициирует ошибку, которая должна быть перехвачена и обработана блоком следующего уровня (в нашем примере это внешний блок Try, но в компонентах ошибка после некоторой предварительной обработки может передаваться клиенту).

 ' Обработка ошибок

' Copyright ®2001 by Desaware Inc. All Rights Reserved

 Imports System.10 

Module Modulel

Const ShowErrors As Boolean = True 

Const ThrowOnBadFormat As Boolean = True

Программа начинается с блока Try. В нашем примере этот блок, прежде всего, гарантирует выполнение команды Console.ReadLine из блока Finally: 

Sub Main()

Dim FileToRead As String 

Try

' Размещение программы в блоке Try позволяет легко 

' перехватить вызов Exit Sub 

FileToRead = CurDir + "\TestFile.txt"

 Dim TestFile As FileStream

Следующий блок Try готовит файл к чтению. Если файл существует, он открывается, а если не существует — программа создает его. При возникновении каких-либо ошибок, препятствующих чтению из файла, программа завершает работу.

В области файлового ввода-вывода в VB .NET также произошли существенные изменения. Эта тема рассматривается в главе 12.

Класс FileStream объявлен производным от Stream — класса, предназначенного для работы с потоками данных. Класс FileStream предназначен для чтения и записи данных в файлах. Если файл существует, объект FileStream успешно создается программой; в противном случае инициируется исключение FileNotFoundException. А как же другие типы ошибок? В этом блоке имеется вторая секция Catch для перехвата обобщенного объекта Exception1.

1 Блок, начинающийся со строки Catch E2 As Exception (см. ниже).

Итак, мы знаем, что тип FileNotFoundException является производным от System. Exception и что System. Exception соответствует любой ошибке, a F1leNotFoundException — только ошибке «файл не найден». Возникает вопрос: из чего следует, что ошибка «файл не найден» будет перехвачена именно обработчиком System. Exception, а не более общим обработчиком System. Exception?

Ответ прост: потому что обработчик FileNotFoundException стоит на первом месте.

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

Try

TestFile = New FileStream(FileToRead, FileMode.Open, FileAccess .Read)

 Catch E As FileNotFoundException

' Если файл не найден, создать новый файл

Если файл не найден, мы создаем новый файл и пытаемся записать в него данные примера. Для этого программа сначала создает новый объект FileStream (на этот раз с установленным флагом Create), а затем создает связанный с ним объект StreamWriter. Методы объекта StreamWriter предназначены для выполнения с потоком различных операций чтения и записи.

После записи данных в поток необходимо вызвать метод Flush. Дело в том, что класс FileStream для повышения быстродействия записывает данные в промежуточный буфер. Мы должны очистить буфер и произвести физическую запись в файл, прежде чем переходить в начало файла и читать записанные данные.

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

Try

TestFile = New FileStream(FHeToRead, FileMode.Create, _

 FileAccess.ReadWrite)

Dim Writer As New StreamWriter(TestFile)

Writer.WriteLine ("8")

Writer.WriteLine ("7")

Writer.WriteLine ("0")

Writer.WriteLine ("ABC")

Writer.WriteLine ("5")

' He забудьте вызвать Flush!!!!

Writer. Flush()

TestFile.Seek(0, SeekOrigin.Begin) 

Catch CantCreate As Exception

1 Маловероятно в нашем примере

Console.WriteLine ("Can't create or write the file")

Exit Sub 

End Try

Catch E2 As Exception

Console.WriteLine ("Some other errror occurred")

' Внимание: блок Finally выполняется

' даже при вызове Exit Sub!

Exit Sub 

End Try

В настоящий момент файл открыт и готов к чтению (в противном случае программа бы уже завершилась). Следующим шагом станет чтение из файла. Мы открываем другой блок Try, но не для того, чтобы перехватывать ошибки, а в первую очередь для определения блока Finally, в котором файл закрывается и удаляется (в нашем примере удаление файла производится в демонстрационных целях). Последовательное чтение записей из файла осуществляется в цикле Do.

 ' Если мы находимся в этой точке, 

' значит, файл TestFile был благополучно открыт.

Try

Dim Reader As New StreamReader(TestFile) Do

Dim I As Integer, S As String

Dim Result As Integer

Для каждой строки файла мы входим в новый блок Try. Почему? Потому что ошибки, происходящие в этом блоке, обычно не являются фатальными — это означает, что обработка ошибки не помешает нам продолжить чтение остальных записей файла.

Try

S = Reader.ReadLine()

 I = Clnt(S)

 Result = 100 \ I 

Console.WriteLine (Result)

С исключением DivideByZeroException все более или менее понятно. Обратите внимание на использование команды Exit Try для выхода из блока Try. Команда Exit Try передает управление строке, следующей за End Try. Тем не менее код в блоке Finally выполняется даже в случае вызова Ехit Тrу. Вы можете поэкспериментировать с этой командой при помощи константы Show Errors1.

Catch DivByZero As System.DivideByZeroException 

If Not ShowErrors Then Exit Try

 Console.WriteLine ("** Divide by zero **")

Исключение System. InvalidCastException происходит, когда строку, прочитанную из файла, не удается преобразовать в целое число. Если константа ThrowOnBadFormat равна True, программа инициирует новое исключение — но какое?

1 Да, я прекрасно знаю: в этом конкретном примере было бы лучше проверять значение ShowErrors при вызове Console.Wri teLine вместо того, чтобы использовать слегка извращенную логику с Exit Try. Но тогда я бы не смог продемонстрировать команду Exit Try! Откровенно говоря, вам вряд ли придется использовать Exit Try, если только код обработки ошибок не отличается особой сложностью.

Вы можете инициировать любое исключение командой Throw. Для этого достаточно создать новый объект Exception и передать его Throw в качестве параметра. При этом можно инициировать исключение стандартного типа или определить свой собственный тип (создайте новый класс, производный от System. Exception или другого класса исключения). В нашей программе заново инициируется исключение InvalidCastException, но с нашим собственным сообщением об ошибке. Более того, вторым параметром передается исходный объект BadConversion. Этот параметр показывает, что инициированное исключение было инициировано в результате другого исключения и содержит дополнительную информацию для внешнего блока.

Catch BadConversion As System.InvalidCastException

If ShowErrors Then Console.WriteLine ("** " + S + " is not a number **")

 If ThrowOnBadFormat Then Throw New _ 

System.InvalidCastException(_ 

"My own exception happened here", BadConversion)

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

Catch OtherErrors As Exception

' Бессмыспенный блок Catch

Throw OtherErrors 

End Try

Что произошло бы, если эти «остальные» ошибки не перехватывались бы в нашей программе?

Ошибки, для которых не был найден подходящий блок Catch, автоматически переходят в следующий блок Try! В итоге все выглядит так, словно мы перехватили ошибку и инициировали ее заново. Короче, этот блок Catch бесполезен и его следовало бы убрать из программы.

В контексте текущего блока Try при вызове Throw (в блоке Catch OtherErrors или при возникновении ошибки System. InvalidCastException с ThrowOnBadFormat = True) ошибка выйдет за пределы блока Try, и чтение файла прекратится. Но что, если ошибка преобразования произойдет при ThrowOnBadFormat = False или будет выполнено деление на 0? Команда Throw не вызывается, чтение файла продолжается, и функция подходит к команде Loop

Loop While Reader.Peek <> -1

Почему мы используем метод Reader . Peek (возвращающий -1 при достижении конца файла)? Почему бы просто не перехватить исключение, возникающее при достижении конца файла, и не прекратить на этом чтение?

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

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

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

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

Следующий блок Catch показывает, как контейнер мог бы обрабатывать инициированные исключения. Он выводит в консольное окно собственное сообщение об ошибке вместе с сообщением, полученным в исключении, и сообщением внутреннего исключения, после чего выводится состояние стека (завидуй, VB6!). 

Catch E As Exception

' Обработка ошибок, возникающих в другой сборке

 Console.WriteLine(ControlChars,CrLf +

"An internal error occurred")

 Console.Writel_1ne("Message: " + E.Message)

  Console.WriteLine("Source Message: " +

E.InnerException.Message) Dim F As Integer, S As New StackTrace(E)

  Console.WriteLine("5tackFrame: ")

 For f = 0 To S.FraroeCount - 1

Console.WriteLine(S.GetFrame(f).ToString())

 Next F

Мы подходим к выходу из блоков Try. Остается лишь проследить за тем, чтобы файл был должным образом закрыт и удален. В нашем примере файл создавался самим приложением, поэтому какие-либо проблемы с его удалением практически исключены. Впрочем, если вас это все же беспокоит, вы всегда можете включить дополнительный блок Try в блок Finallу. В нашем примере все ошибки, возникающие на этой стадии, будут перехвачены блоком Catch внешнего уровня. Приложение завершается итоговым вызовом Console. ReadLine:

 Finally

' Закрытие файла практически всегда

1 завершается удачно.

TestFile.Close()

' Попытка удаления может оказаться неудачной,

' если файл защищен системой безопасности.

File.Delete (FileToRead) 

End Try

Catch Unhandled As Exception

' На случай, если мы что-то просмотрели.

' Вы сами выбираете, какие ошибки передаются наружу!

Console.WriteLine ("An unhandled exception occurred " + _

Unhandled.Message)

 Finally

Console.ReadLine() 

End Try 

End Sub

End Module

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

  •  Поэкспериментируйте со значениями констант ShowErrors и ThrowOnBadFormat.
  •  Добавьте команды Throw, инициирующие разные ошибки в разных местах.
  •  Включите команды Exit Sub в блоки Catch и убедитесь в том, что блок Finalize выполняется всегда.
  •  Включите команды Exit Try в блоки Catch и убедитесь в том, что блок Finalize выполняется всегда.
  •  Включите секции When в команды Catch, чтобы реализовать проверку дополнительных условий в обработчиках ошибок.

А как же On Error Goto?

Visual Basic .NET продолжает поддерживать старый синтаксис On Error Goto и объект Err. Возможно, разработчики из Microsoft решили, что изменения в языке и так достаточно масштабны, и им не захотелось брать на себя новые хлопоты. А может, это был акт милосердия по отношению к тем программистам, которым придется адаптировать готовые программы VB6, поскольку код структурной обработки ошибок радикально отличается от кода, использующего On Error Goto. А может, это объясняется практическими соображениями: проектирование Upgrade Wizard, правильно преобразующего On Error Goto в эквивалентные конструкции Try...Catch, было бы чрезвычайно сложной задачей.

Если вам приходится часто адаптировать готовый код и вы знаете, как работает механизм обработки ошибок в вашей программе, возможно, стоит ограничиться применением On Error Goto — преимущества структурной обработки исключений лучше всего проявляются на стадии исходного проектирования.

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

 

Другие изменения в языке

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

Передача управления

Когда вы в последний раз пользовались конструкциями Gosub, On...Gosub или On...Goto?

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

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

Конструкция While...Wend теперь превратилась в блок While...End White. К этому второстепенному изменению легко привыкнуть, а язык становится более логичным и последовательным.

Объединение строковых функций

Многие функции VB6 существуют в двух версиях: одна возвращает Variant, а другая возвращает строку. Эти функции перечислены в табл. 9.1.

Таблица 9.1. Строковые функции VB6, существующие в двух версиях

Chr

ChrS

CurDir

CurDirS

Dir

Dir$

Format

Formats

Hex

Hex$

LCase

LCaseS

Left

LeftS

LTrim

LTrimS

Mid

MidS

Oct

OctS

Right

RightS

RTrim

RTrimS

Space

SpaceS

Trim

TrimS

UCase

UCase$

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

В VB .NET все эти функции существуют в одной форме и всегда возвращают только строки (что вполне логично, поскольку Variant больше не существует).

Перед нами еще один пример разумных изменений в языке.

 

Другие второстепенные изменения

В VB .NET функция UBound для массива нулевой длины возвращает -1. Изменения в работе с массивами в VB .NET описаны в главе 8.

Исчезнувшие команды

Первоначальный успех Visual Basic был в значительной мере обусловлен его новаторским подходом к Windows-программированию, а замечательные возможности VB проистекали от инкапсуляции Win32 API на уровне самого языка. Последнее обстоятельство и позволяло легко создавать приложения Windows в Visual Basic. Отчасти это упрощало задачу программиста, так как вместо десятков хитроумных функций API было достаточно изучить несколько команд VB. К счастью, вы также могли прибегать к помощи Win32 API в тех случаях, когда речь шла о нетривиальных возможностях уровня ОС.

Шли годы. Visual Basic развивался, обогащаясь новыми командами и возможностями. Возможно, VB6 до сих пор остается самым удобным средством создания Windows-приложений, но «простым» его уже не назовешь. В наши дни изучение Visual Basic потребует от программиста немалых усилий.

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

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

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

1 Не подумайте, будто я жалуюсь. Я уважаю выбор Microsoft, и мои собственные программы от этого только выиграют. Просто читатель должен четко представлять себе все «плюсы» и «минусы».

 

Графические команды

Следующие методы и свойства Visual Basic 6 не поддерживаются в VB .NET: 

PSet, Scale, Circle и Line.

Соответствующие возможности (или их аналоги) находятся в пространствах имен System. Drawing и System.Graphics. Адаптация кода, использующего эти команды и методы, рассматривается в главе 12.

Не торопитесь скорбеть об утрате. Позвольте напомнить вам синтаксис метода VB6 Line:

 объект.Line [Step] (xl, yl) [Step] - (x2. y2), [цвет], [В][F]

Этот синтаксис абсолютно не соответствует общему синтаксису других графических методов. И кому только это в голову пришло?

Решившись на «чистку языка», разработчики Microsoft были вынуждены исключить из него многие знакомые элементы. Конечно, это означает, что нам придется многое учить заново. Однако сам термин «чистка» подразумевает, что изменившиеся аспекты языка были «нечистыми», и во многих случаях это действительно так.

 

Команды, связанные с типом Variant

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

Функции IsNull и IsEmpty стали бессмысленными. Чтобы узнать, соответствует ли объект ссылочного типа значению Nothing, можно воспользоваться оператором Is:

If obj Is Nothing Then...

Объект структурного типа можно обнулить (как и все его члены), однако сама переменная всегда существует и всегда действительна.

Функция IsObject бессмысленна: любая переменная в VB .NET является объектом, поэтому эта функция всегда возвращала бы True. Чтобы узнать, соответствует ли переменная ссылочному или структурному типу, можно воспользоваться механизмом рефлексии (см. главу 11).

Как будет показано в главе 11, функции VarType и TypeName также заменяются средствами рефлексии.

 

Математические функции

.NET Framework содержит ряд специализированных классов для выполнения математических операций. Из VB .NET были исключены функции Abs, Atn, Cos, Exp, Log, Sgn, Sin, Sqr, Tan, Rnd и Round; теперь вам придется напрямую работать с математическими библиотеками. Некоторые имена функций System.Math не имеют точного соответствия с именами функций VB6 — Atan вместо Atn, Sign вместо Sgn и Sqrt вместо Sqr.

Ниже приведен проект MathVBG, демонстрирующий применение математических функций в VB6. 

Option Explicit

Sub Main()

Randomize

Debug.Print Rnd()

Debug.Print Sqr(4)

Debug.Print Round(1.4), Round(1.6)

Debug.Print Sgn(-l), Sgn(0), Sgn(l)

Debug.Print Atn(0) 

End Sub

В результате обработки этого кода мастером Upgrade Wizard (и его преобразования в консольное приложение ради ясности) получается следующий код VB .NET:

Option Strict Off

Option Explicit On

Module modMath

' ПРЕДУПРЕЖДЕНИЕ: Приложение завершится при выходе

' из функции Sub Main().

' Дополнительная информация:

1 ras-help://MS.MSDNVS/vbcon/html/vbupl047.htm

Public Sub Hain()

Randomize()

Console.WriteLine (Rnd())

Console.WriteLine (System.Math.Sqrt(4))

Console.WriteLine (VB6.TabLayout(System.Math.Round(1.4),

System.Math.Round(1.6))) Console.WriteLine

  (VB6.TabLayout(System.Hath.Sign(-l),

Systern.Math.Sign(0), System.Math.Sign(l)))

 Console.WriteLine (System.Math.Atan(0))

 End Sub 

End Module

Начнем с функций Sqr, Round, Sgn и Atn, для которых существуют непосредственные аналоги — Systern.Math.Sqrt, Systern.Math.Round, Systern.Math.Sign и System.Math.Atan.

Невольно хочется спросить, зачем было исключать из языка исходные функции, если они так тесно связаны с методами System. Math? Зачем заставлять программистов VB .NET пользоваться библиотекой System.Math?

Думаю, это было сделано для того, чтобы подтолкнуть программистов VB к использованию богатого набора функций библиотеки System.Math. В пространстве имен System .Math имеется множество полезных элементов, среди которых функция Log 10 и встроенное значение Pi (которые в VB6 приходилось вычислять отдельно, для чего в электронной документации даже приводились специальные формулы). Несомненно, объединение всех математических функций в одном месте выглядит вполне логично, будь то сам язык или отдельное пространство имен. Microsoft выбрала второй вариант, и мне трудно осуждать этот выбор.

С функцией Rnd дело обстоит сложнее. Ей соответствует объект VBMath из пространства имен Microsoft.VisualBasic, однако вы можете воспользоваться классом System. Random, обладающим более гибкими возможностями, чем функция Rnd. В некоторых случаях Upgrade Wizard выбирает встроенную функцию, тогда как вы, возможно, предпочтете использовать один из общих системных классов. Мы еще вернемся к этой теме.

 

Другие команды

Функции LSet и RSet работают только со строками. В .NET присваивание структур выполняется на уровне присваивания полей.

Класс Debug заменяется классом System.Diagnostics.Debug, а метод Debug. Print заменяется методами System.Diagnostics.Debug.write/WriteLine.

Функция String$ заменена конструктором класса String (см. главу 8).

Пространства имен Microsoft.VisualBasic и Compatibility

В маркетинговых материалах Microsoft, посвященных .NET Framework и Visual Basic .NET, часто повторяется утверждение о том, что Visual Basic .NET является «первоклассным» .NET-языком. Важность этого утверждения трудно оценить сразу. Для программистов Visual Basic это в первую очередь означает то, что VB .NET не является побочной ветвью, «игрушечным» или «неполноценным» языком в среде .NET. Microsoft рассматривает Visual Basic .NET как нормальный .NET-язык, позволяющий в полной мере использовать возможности .NET по созданию CLS-совместимого кода (по мнению Microsoft, это единственный тип кода, который вообще должен создаваться в .NET1).

Одно дело — видеть эти утверждения в рекламных брошюрах или на слайдах PowerPoint, и совсем другое — рассматривать их с технологической точки зрения. Ничто не доказывает это лучше, чем пространство имен Microsoft. VisualBasi с.

Возьмем для примера такой язык, как C++ или С. Синтаксис обоих языков очень прост и содержит очень мало ключевых слов. По историческим причинам большая часть функциональности этих языков обеспечивается громадными исполнительными библиотеками, будь то исполнительная библиотека С, библиотека классов MFC или ATL. В мире .NET синтаксис этих двух языков, как и синтаксис нового языка С#, остается очень простым, а функциональность обеспечивается библиотекой классов .NET.

Просто любопытства ради запустите Object Browser и откройте пространство имен Microsoft.VisualBasi с. В нем отсутствуют некоторые ключевые слова VB (такие, как If, Then или For), но зато встречаются практически все остальные команды, функции и перечисляемые значения VB.

Другими словами, большинство команд Visual Basic обеспечивается пространством имен .NET Framework, и это относится ко всем остальным .NET-языкам.

Чтобы лучше продемонстрировать сказанное, рассмотрим приложение CSharpOrVB (листинг 9.9). Это приложение написано на С# и содержит ссылку на пространство имен Micros of t .Vi sual Basic2.

Листинг 9.9. Приложение CSharpOrVB

// Приложение CSharpOrVB 

// Copyright ©2001 by Desaware Inc.

 using Microsoft.VisualBasic;

 namespace CSharpOrVB 

using System;

/// <summary>

/// Summary description for /font>

/// </summary> 

bsp;

{

static void Main(string[] args) 

{

String s = "This is a test"; Console.WriteLine(Strings.Mid(s, 6, 4)); Console. ReadlineO ;

}

 }

 }

1 Я разделяю это мнение.

2 Не забудьте включить в проект ссылку на исполнительный модуль Microsoft.VisualBasic командой Project > Add References.

Все верно. Visual Basic .NET и С# настолько тесно связаны с .NET Framework, что VB .NET не только может использовать те же классы, что и С#, но и С# может работать с классами и командами Visual Basic .NET!

Не знаю, как вы, а я был просто поражен.

Пространство имен Microsoft.VisualBasic содержит функции и методы Visual Basic .NET, а также ряд элементов, упрощающих «программирование в стиле VB6» в Visual Basic .NET.

Дело мастера боится

Многие новички в процессе освоения VB .NET пишут программы VB6 и смотрят, какой код будет сгенерирован мастером Upgrade Wizard. К сожалению, этот мастер сбрасывает флажок Option Strict, поэтому такие уроки нередко оказываются вредными.

В качестве примера рассмотрим приложение XOrVBG. 

Option Explicit

Sub Main()

 Dim Varl As Integer 

Dim Result As Integer

Result = Varl Xor 5

End Sub

После обработки этого кода мастером Upgrade Wizard вы получите следующий результат:

Option Strict Off

Option Explicit On

Module XOrMod

1 ПРЕДУПРЕЖДЕНИЕ: Приложение завершится при выходе

' из функции Sub Main().

' Дополнительная информация:

' ms-help://MS.MSDNVS/vbcon/html/vbupl047.htm

Public Sub Main() 

Dim Varl As Short 

Dim Result As Short

Result = Varl Xor 5

End Sub 

End Module

Теперь попытайтесь установить флажок Option Strict. При компиляции программы возникает ошибка, обусловленная неявным преобразованием типа Integer в Short. Существует много других ситуаций, в которых Upgrade Wizard генерирует код с неявным преобразованием типов или вызовами методов с поздним связыванием, при которых флажок Option Strict должен быть сброшен. Все эти преобразования и вызовы методов сопряжены с потенциальными ошибками времени выполнения — ошибками, которые можно было бы исключить из приложения на стадии проектирования при помощи жесткой проверки типов.

Если вы занимаетесь адаптацией готового кода, вероятно, применение Upgrade Wizard является неизбежным первым шагом. Тем не менее, как я упоминал в части 1 этой книги, адаптация во многих случаях экономически не оправдана. VB .NET лучше подходит для написания новых программ.

 

Компромиссы совместимости

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

Если вы ожидаете, что вам когда-нибудь придется переводить свое приложение на другой язык (скажем, на С#), я бы не советовал использовать традиционные функции VB .NET, даже при том, что их можно вызывать из С# (см. выше), и вообще не существует сколько-нибудь разумных причин для перевода программ VB .NET на С#.

В таких случаях я рекомендую придерживаться традиционных функций VB .NET (из пространства имен Microsoft.VisualBasic)H избегать лишь функций из пространства Microsoft.VisualBasic.Compatibili4y.VB6.

Почему? Потому что функции пространства имен Microsoft.VisualBasic (исключая пространство Compatibility. VB6) пережили «чистку» языка, а функции Compatibility.VB6 являются пережитками, поддерживаемыми ради Migration Wizard.

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

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

Снова о Rnd

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

Функция Rnd возвращает вещественное число в интервале от 0 до 1 (исключая 1).

У функции Rnd не существует прямого аналога в .NET Framework. Вместо этого в .NET существует класс System.Random. После создания экземпляра этого класса можно использовать его методы для получения случайных чисел.

Next — возвращает случайное целое число (32-разрядное).

NextDouble — возвращает случайное число от 0 до 1 (исключая 1).

NextBytes — заполняет массив Byte случайными данными.

Генератор можно раскрутить, указав начальное значение при вызове конструктора System.Random; по умолчанию используется начальное значение, основанное на текущей дате и времени (следовательно, отпадает необходимость в команде Randomize).

Перед нами отличный пример того, почему Upgrade Wizard выбирает реализацию с использованием исполнительной библиотеки VB .NET. Поскольку объект System.Random не имеет метода с аналогичными возможностями, имитация Rnd должна состоять из нескольких строк программного кода, поэтому при преобразовании кода из VB6 в VB .NET гораздо проще воспользоваться встроенной функцией.

Тем не менее в новых программах рекомендуется использовать класс System.Random и избегать лишних затрат, связанных с применением Rnd.

Константы

В .NET Framework, как и в Win32 API и VB6, широко используются константы — математические величины (такие, как Pi), цветовые (например, Red, Green, Blue) и флаги операций (скажем, открытия файла).

Различия заключаются в области видимости этих констант.

В .NET константы являются объектами перечисляемых типов или общими свойствами классов. Допустим, вы хотите завершить строку комбинацией символов CR+LF.

Следующие две команды эквивалентны:

а = а + ControlChars.CrLf

 а = а + Constants.vbCrLf

В пространство имен Mi с rosoft.VisualBasic. Constants входят многие (но не все) константы с префиксом vb, от символов до параметров окон сообщений. Пространство Microsoft.VisualBasic.ControlChars содержит только управляющие символы.

Аналогично, вместо использования старых цветовых констант VB vbRed и vbBlue можно воспользоваться константами из объекта System. Drawi ng. Color.

Конечно, прямые ссылки на константы .NET потребуют определенных усилий с вашей стороны, но если вы к ним привыкнете, это упростит не только чтение, но и сопровождение вашей программы. Почему? Да потому, что перечисления являются типизованными переменными, а функция, получающая параметр перечисляемого типа, сможет получать лишь константы из соответствующего набора. Например, функция MsgBox примет только значения из перечисления MsgBoxStyle. Более того, значения перечисляемого типа будут выводиться в окне подсказки, что упростит программирование.

В Visual Basic 6 имена констант должны были начинаться с префикса vb, поскольку при каждом добавлении в проект ссылки на новую библиотеку ее константы объединялись с глобальным пространством имен приложения. Если одно приложение определяло константу FAILED, равную -1, а затем в другом приложении эта константа определялась равной 0, окончательное значение FAILED в вашем приложении зависело от того, какая библиотека типов стояла на первом месте в списке ссылок, что открывало массу возможностей для ошибок и путаницы. В Visual Basic (и многих библиотеках СОМ) эта проблема решалась добавлением префиксов (предположительно уникальных) к именам констант. В .NET иерархическое строение пространств имен снижает вероятность ошибки, поскольку при импортировании пространства имен, использующего перечисляемые типы, имя перечисления включается во все упоминания констант этого типа в программе. Единственным исключением является явное импортирование перечисляемого типа, но оно предполагает, что программист действует сознательно.

 

Строки и совместимость

Строковые операции традиционно считались одной из сильных сторон языка BASIC (и всех его производных). .NET Framework тоже содержит классы и объекты, предназначенные для выполнения строковых операций.

Я бы рекомендовал вам придерживаться знакомых методов Visual Basic. Они просты и удобны, причем обычно у них имеются прямые аналоги среди методов .NET (а иногда они даже обладают возможностями, не предусмотренными в .NET). Единственным исключением являются области, критические по быстродействию. В таких случаях вам придется сравнить разные варианты и проверить, не обеспечивают ли объекты .NET более высокого быстродействия. Например, при построении строк посредством конкатенации класс StringBui Ider (разработанный специально для этой цели) работает гораздо быстрее, чем стандартный синтаксис объединения строк.

Обязательно познакомьтесь с классами .NET Framework, перечисленными в табл. 9.2.

Таблица 9.2. Строковые классы в .NET

Пространство имен

Содержимое

System.String

Базовый класс для всех строк VB .NET. Содержит методы для выполнения общих операций со строками

System.Text.Encoding

Класс для преобразования строк мget кодировками ASCII, Unicode и т. д.

System.Text.StringBuilder

Класс для построения и модификации строк. В отличие от строк содержимое объектов StringBuilder не является неизменным

System.Text. RegularExpressions

Мощный класс для лексического анализа и обработки строк с применением регулярных выражений1

Как упоминалось выше, функция Stri ng$ была заменена конструктором объекта String.

Файловый ввод-вывод и совместимость

В пространстве имен Microsoft.VisualBasic по-прежнему поддерживаются традиционные файловые операции ввода-вывода с применением команд Open, Close, Get, Put, Input, Print и т. д.

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

Пространство .NET Framework System. 10 объединяет мощные средства потокового ввода-вывода. Подробное описание классов этого пространства приведено в электронной документации .NET. Другое, менее формальное изложение можно найти в документации Visual Studio .NET (раздел «Visual Studio .NET/Visual Basic and Visual C#/Visual Basic Programming/ Processing Drives Folders and Files/File, Drive and Folder Access with Visual Basic .NET»).

Пример FilelO дает представление о том, как в VB .NET организуется чтение содержимого файла в текстовое поле. В листинге 9.10 класс OpenFileDialog использован для открытия стандартного диалогового окна. После ввода имени мы средствами класса OpenFileDialog получаем объект Stream для указанного файла. Для чтения содержимого файла используется отдельный объект StreamReader. Запутались? Еще бы, к этому нужно привыкнуть. В главе 12 я попытаюсь хотя бы частично разъяснить, как работают классы файлового вывода в VB .NET.

Листинг 9.10. Чтение содержимого файла из приложения FileIO

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

ByVal e As System.EventArgs) Handles menultem2.Click 

openFileDialoglO = New OpenFi leDialogO openFileDialogl() .

 InitialDirectory = "c:\" 

openFileDialogl()

Filter = "txt files (*.txt)|*.txt"

 openFileDialogK) .Filter-Index = 1 

openFileDialoglO .RestoreDi rectory = True 

If openFileDialogK) .ShowDialog() = DialogResult() .OK Then

Dim fs As Stream

fs = openFileDialoglO .OpenFile()

Dim sr As New StreamReader(fs)

textBoxK) .Text = sr .ReadToEnd()

textBoxlQ.SelectionLength = 0

 End If

 End Sub

Итоги

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

Кроме того, в этой главе описан новый синтаксис обработки ошибок с применением исключений, который по своим возможностям заметно превосходит старую команду VB6 On Error Goto— даже дети знают, что команда Goto вредна1.

О вреде Goto известно уже давно. Пришло время закрыть и эту лазейку. Если эта тема вас заинтересовала, обращайтесь к классической статье Эдгара Дейкстры (Edsger W.Dijkstra) «Goto Statement Considered Harmful», в настоящее время доступной на web-сайте Ассоциации вычислительной техники по адресу http://www.acm.org/.

Во многих ситуациях можно использовать как библиотеки языка VB .NET, так и библиотеки классов .NET. He существует четких правил, по которым можно было бы определить, какое средство следует применять в каждом конкретном случае. Но если в процессе обучения вы при помощи мастера VB .NET Upgrade Wizard обновляете код VB6, просматриваете результат и видите, что в полученном коде используется библиотека Microsoft.VisualBasic.Compatibility.VB6, — откажитесь от него! Лучше потратьте немного времени и решите эту задачу с использованием традиционных функций VB или классов .NET Framework.

Назад   Вперёд

 



Заказ программ!
Вы можете заказать у меня написание необходимой вам программы. Чем популярнее будет она, тем меньше стоит работа.
Инфо
Сайт создан: 3 февраля 2000 г.
Рейтинг@Mail.ru
Главная страница