Главная страница Visual 2000 · Общий список статей
Публикации в журнале BYTE/Россия · Публикации на тему MS .NET

А ты готов к Visual Basic.NET?
Советы тем, кто собирается работать с новой версией VB
Часть 2

Андрей Колесов

© Андрей Колесов, 2001
Авторский вариант. Статья была опубликована c незначительной литературной правкой в журнале BYTE/Россия N 6/2001.
Внимание! Статья отражает знание автором предмета на момент ее публикации! Использовалась версия VB.NET beta 1!

Начало: Часть 1
Продолжение: Часть 3

Прежде чем перейти к обсуждению новшеств VB.NET

Прежде чем перейти к обсуждению новшеств VB.NET, нужно отметить, что в мировом (в первую очередь, американском) сообществе VB-программистов продолжаются весьма эмоциональные споры вокруг будущей версии Visual Basic. По оценкам экспертов журнала Visual Basic Programmer's Journal большинство разработчиков одобряют технологию .NET, но их благодарность корпорации Microsoft никак нельзя назвать безграничной.

Тем не менее, можно констатировать — многомиллионная (по некоторым оценками от 2 до 4 млн. человек) армия пользователей VB инструмента разделилась на две части.

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

При этом понятно, что накал дискуссий определяется не эмоциональными моментами, а чисто практическими интересами — все понимают, что реализация новшеств VB.NET может серьезно повлиять на их личную судьбу. В этой связи имеются опасения, что переход от нынешней архитектуры Windows к будущей .NET может быть таким же болезненным, как в начале 90-х от DOS к Windows: значительная часть DOS-программистов просто не смогла (в том числе, по чисто психологическим причинам) адаптироваться в новым методам разработки.

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

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

А теперь перейдем к советам...

В начало статьи

Совет 17. Внимание при определении массивов

При работе с массивами VB-программистов ожидают целый ряд сюрпризов, причем не очень приятных. Общие рекомендации таковы: исключите использование оператора Option Base 1 (его нет в VB.NET), применяйте массивы с нулевой нижней границей индекса и пересмотрите варианты динамического определения массивов.

Приверженцы истинного Basic <см. примечание> могут радоваться — мы возвращаемся в далекое прошлое: теперь в "родном" варианте VB.NET поддерживает только массивы с нулевой нижней границей. Но как раз здесь программистов ожидает подводный камень — одна и та же конструкция в VB.NET и всех предыдущих версиях будет работать по-разному.

Примечание Кстати, в середине 80-х годов Томас Курц, один из создателей первого Бейсика (это было в 1964 году), разработал систему, которую назвал True Basic. Таким именем он хотел подчеркнуть то, что многие другие Basic-инструменты отошли от начальных канонов этого языка.

То есть фрагмент кода


 Dim Arr(4)
 For i = LBound(Arr) To 4
   Arr(i) = 0
 Next

который работал всегда безукоризненно, в VB.NET выдаст ошибку при i = 4.

Дело в том, что определение массива:


Dim Arr(N)

во всех версиях Basic интерпретировалась как


Dim Arr (0|1 To N)

(0|1 — в зависимости от оператора Option Base), т.е. N указывало верхнюю границу индекса.

В VB.NET это будет указывать ЧИСЛО ЭЛЕМЕНТОВ:


Dim Arr (0 To N-1)

Тут полезно напомнить, что VB ранее допускал два формата описания размерности массива (на примере одномерного):


Dim Arr(N)

и


Dim Arr(lowIndex To hightIndex)

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


Dim Arr(0 To N)

Затем, чтобы обеспечить совместимость с Фортраном (там нумерация начиналась с 1) был введен управляющий оператор Option Base 0|1, который позволял устанавливать нижнюю границу с нуля или единицы. Но это породило проблему неопределенности. Например, при копировании фрагмента кода Dim Arr(N) в некий модуль, конкретное значение нижней границы массива определялось оператором Option Base именно этого модуля. В одном случае это мог быть 0, в другом — 1.

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


For i = LBound(Arr) To UBound(Arr)
  Arr(i) = 0
Next

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


 Dim a(1 To 10) As Integer

поменяется на


 Dim a As Object = New VB6.Array(GetType(Short), 1, 10)

Проблема тут заключается в том, что такие массивы работают заметно медленнее по сравнению с "родными" и имеют некоторые ограничения на их применение. Например, wrapper- массивы нельзя передавать в Си-классы и в процедуры, использующие параметры типа Array.

Что касается динамического определения массива, то теперь конструкция


 Dim v
 ReDim v(10)   ' работает в VB 6.0

работать не будет — переменная сразу должны быть определена как массив:


 Dim v(2)
 ReDim v(10)   ' работает в VB.NET

В начало статьи

Совет 18. Откажитесь от неявного преобразования типов данных

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

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


 Dim newDelete As Boolean
 If newDelete Then
   chkDeleteMe.Value = 1
 Else
   chkDeleteMe.Value = 0
 End If

использует более короткий код:


 chkDeleteMe.Value = -1 * newDelete

Тут надо иметь в виду несколько явных минусов второго варианта:

  1. Хотя он выглядит короче, но точки зрения создаваемого машинного кода — менее эффективен, по крайней мере, по скорости выполнения.

  2. Результат его работы неочевиден. Не уверен, что любой VB-программист сходу ответит, каков будет результат при DeleteMe = True.

  3. В VB.NET он не будет работать (об этом ниже).

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


strR1$ = Str$(2.34)
strR2$ = 2.34 
Print strR1$, strR2$    ' будет напечатано 2.34  2,34

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

В начало статьи

Совет 19. Забудьте о целочисленных значениях булевых переменных

Программный код


 Dim i As Integer
 i = True
 If i = -1 Then
   MsgBox "Истина"
 Else
   MsgBox "Ложь"
 End If

будет выдавать в VB 6.0 "Истина", а в VB.NET — "Ложь". Причина этому — целочисленное значение True поменялось с -1 на 1. Рекомендации очевидны: используйте только тип данных Boolean для логических переменных и при работе с ними применяйте только имена констант True и False. То есть приведенный выше пример должен быть записан так:


 Dim i As Boolean
 i = True
 If i = True Then
   MsgBox "Истина"
 Else
   MsgBox "Ложь"
 End If

С одной стороны, изменение числового значения для True выглядит достаточно логично, так булева алгебра всегда строилась на логике 0/1 (Ложь/Истина). К тому же это устраняет важное расхождение с языком Си. Но с другой стороны, это позволяет отметить некоторые внутренние проблемы VB.

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


Dim a As Integer a = 0  ' ложь a = Not a

приводил к результату -1.

Тут отметим одно противоречие Basic: на самом деле целочисленные переменные выступают в виде, то чисел со знаковым разрядом (в арифметических операциях и при использовании десятичных литералов), то без него (в логических операциях и в шестнадцатиричных и восьмеричных литералах).

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


 Dim intVar As Integer
 Dim blnVar As Boolean
 ...
 blnVar = intVar

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


 Dim a As Integer
 Dim b As Integer
 Dim c As Boolean
 a = 5
 c = a
 b = c
 Print a; b

В VB 6.0 будет напечатано "5 -1", VB.NET — "5 1".

Не говоря уже о неочевидности результата такого кода:


 Dim a As Integer
 Dim c As Boolean
 a = 5
 c = 0
 MsgBox c Or a

Кто из читателей сможет, глядя на этот код, уверенно сказать результат операции?

В начало статьи

Совет 20. Нужно готовиться к разделению логических операций

Описанные выше проблемы совместной обработки логических и целых переменных связаны еще и с тем, что, например, ключевое слово And выступает в зависимости от контекста в роли или логического или побитового And. В VB.NET эта неопределенность устранена — And соответствует только логической операции с переменными типа Boolean, а для побитовых преобразований целых чисел будут использоваться новые операторы — BitAnd, BitOr, BitNot, BitXor.

Следствием этого станет то, что приведенный выше код (с вопросом о полученном результате) в VB 6.0 будет выдавать 5, а в VB.NET — True (1).

Покажем это же на примере следующего кода:


 Dim a As Integer
 Dim b As Integer
 Dim c As Boolean
 a = 1
 b = 2
 c = a And b
 MsgBox "Результат = " & c

В VB 6.0 будет получен ответ False (выполнялось поразрядное логическое умножение двух целочисленных переменных с последующим преобразованием в логическое значение), а в VB.NET — True (целые числа сначала были преобразованы в логические, а потом с ними выполнили операцию And).

Для обеспечения совместимости кода при миграции VB.NET включает функции VB6.And, VB6.Or и VB6.Not, которые эквивалентны существующим сегодня And/Or/Not (почему-то нет для Xor). Соответственно средства обновления кода преобразуют приведенный выше фрагмент в такой вид:


 Dim a As Integer
 Dim b As Integer
 Dim c As Boolean
 a = 1
 b = 2
 c = VB.And(a, b)
 MsgBox "Результат = " & c

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


 Dim a As Integer
 Dim b As Integer
 Dim c As Boolean
 a = 1
 b = 2
 c = (a <> 0) And (b <> 0)
 MsgBox "Результат = " & c

В этом случае никаких проблем с переносом кода не будет, и средства автоматического обновления просто не понадобятся.

В начало статьи

Совет 21. Уделяйте еще больше внимания логическим операциям

В VB.NET будет повышена "интеллектуальность" логических операций для повышения их быстродействия. Это означает, что вычисление:


 Dim a As Boolean
 Dim b As Boolean
 Dim c As Boolean
 a = b And c

будет завершено досрочно (с результатом False), если окажется, что b = False. Это очень хорошо, но тут есть подводный камень, который виден на таком примере:


 Dim b As Boolean
 b = Function1() And Function2()

Дело в том, что если значение функции Function1 окажется False, то обращения к Function2 просто не будет (а это может быть необходимым). Средства миграции выполнят замену кода:


 Dim b As Boolean
 b = VB6.And (Function1(), Function2())

но надежнее сразу записать программу в таком виде:


 Dim b As Boolean
 Dim c As Boolean
 Dim d As Boolean
 c = Function1()
 d = Function2()
 b = c And d

В начало статьи

Совет 22. Используйте внутренние константы

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


 Me.WindowState = vbNormal
 MsgBox "Error!", vbCritical
 txtTextbox.Visible = True

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


 x = 16
 Me.WindowState = 0
 MsgBox "Error!", x
 txtTextbox.Visible = -1

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


 x = vbCritical
 ...
 MsgBox "Error!", x

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

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


 a = a + 2
 b = b + 2
то даже для двух переменных имеет смысл написать код следующим образом:


 Const myStep = 2
 a = a + myStep
 b = b + myStep

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

В начало статьи

Совет 23. Для работы с датами используйте только тип Date

Во всех существовавших до сих пор версиях VB для хранения даты фактически использовались переменные типа Double (отдельный тип Date появился только в версии 4.0). Дата записана в числовом формате IEEE, при этом целая часть соответствует номеру суток, начиная от 30 декабря 1899 года, а дробная — времени суток. Возможно, этот формат сохранен и в VB.NET, но вольность в преобразовании дат с обработкой их как обычных чисел будет прекращена. Подобные нелепые конструкции, вполне допустимые в VB 6.0:


 Dim a As Date
 Dim d As Double
 a = 1101.27
 MsgBox a   ' будет выдано 05.01.1903 6:28:48
 d = Now * 1.4 + 0.347
 a = d
 MsgBox a   ' будет выдано 29.09.2041 4:48:35

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

В начало статьи

Совет 24. Не используйте недокументированные функции

Многие VB-программисты даже не подозревают о существовании недокументированных функций VarPrt, VarPrtArray, VarPrtStringArray, ObjPrt и StrPrt, которые позволяют получать значения адреса памяти, где хранятся соответствующие переменные. Их применение порой очень полезно при работе с Win API, в частности, когда нужно выполнять изощренные операции с буферами.

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

В начало статьи

Совет 25. Не используйте LSet для структур

В VB 6.0 оператор LSet мог использоваться для присвоения переменной одного пользовательского типа значения переменной другого пользовательского типа. Теперь такая возможность будет исключена.

В начало статьи

Совет 26. Лучше не использовать строки фиксированной длины

В VB.NET строки фиксированной длины не будут "родными" (не будут входить в состав базового набора типов данных непосредственно поддерживаемых транслятором). Для обеспечения совместимости будет использоваться специальный объект, поэтому код VB 6.0:


Dim MyFixedLengthString As String * 12

будет преобразован в


Dim MyFixedLengthString As New VB6.FixedLengthString(12)

Но следует иметь в виду, что применение такого объекта будет уже ограниченным. Например, его нельзя будет использовать при работе с функциями Win API. Таким образом, например, вместо такого варианта резервирования буфера:


Dim Buffer As String * 25

нужно писать:


 Dim Buffer As String
 Buffer = String$(25, " ")

В начало статьи

Совет 27. Внимание при использовании строк и массивов в структурах

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


 Private Type UserType
   UserArray(10) As Integer
   UserFixedString As String * 30
 End Type
 Sub UserProc()
   Dim UserVariable As UserType
 End Sub

на такой вариант


 Private Type UserType
   UserArray() As Integer
   UserFixedString As String
 End Type
 Sub UserProc()
   Dim UserVariable As UserType
   ReDim UserVariable.UserArray(10) As Integer
   UserVariable.UserFixedString = String$(30, " ")
 End Sub

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

В начало статьи

Совет 28. Не используйте As Any при обращении к Win API

Множество функций Windows API имеют возможность принимать параметры различных типов - - интерпретация передаваемых данных выполняется в зависимости от значения других параметров. Примером такой функции является SendMessage, в которой в качестве последнего параметра может быть как целое число, так и строка байтов произвольной длина. Для таких случаев VB использует описание подобных параметров As Any, которое говорит о том, что в стек будет помещен некоторый адрес буфера памяти. Фактически это означает, что компилятор снимает с себя ответственность за контроль передаваемых данных.

Практически во всех рекомендациях по работе с Win API говорится о необходимости особого внимания при использовании описания As Any и дается совет использовать нескольких псевдонимов (Alias) с созданием двух и более объявлений для одной и той же функции, причем в каждом из описаний указываются параметры определенного типа. Для VB.NET это будет уже не пожелание, а требование — в нем исключена возможность использования типа As Any.

Далее мы рассмотрим возможности применения As Any, подстерегающие здесь опасности и варианты использования Alias на примере функции lread:


Declare Function lread Lib _
 "kernel32" Alias "_lread" _
  (ByVal hFile As Long, lpBuffer As Any, _
  ByVal wBytes As Long) As Long

В VB их аналогами является оператор Get при чтении файлов типа Binary. Обратим сразу внимание на необходимость использования ключевого слова Alias в объявлении функции. Настоящие названия функции в библиотеке начинаются с символа подчеркивание (типичный стиль для языка C), что не разрешается в VB.

В данном случае параметр lpBuffer является именно адресом буфера, куда буду читаться данные (wBytes указывает число читаемых байтов). В качестве буфера может выступать простая переменная, массив, строка.


 ' чтение вещественного числа, 4 байта
 Dim MyVar As Single
 wBytes = lread (hFile&, MyVar, Len(MyVar)
 ...
 ' чтение  10 элементов массива
 Dim MyArray(0 To 9) As Byte
 wBytes = lread (hFile&, MyArray(0), Len(MyArray(0))* 10)

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

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


' чтение  символьной строки, 10 символов 
Dim MyVar As String MyVar = Space$(10) 
wBytes = lread (hFile&, ByVal MyVar, Len(MyVar))

Здесь видно важное отличие от приведенного ранее примера — строковая переменная обязательно сопровождается ключевым словом ByVal. Если мы этого не сделаем, то будет передавать не адрес строки, а адрес ее дескриптора и фатальная ошибка в этом случае будет неизбежна.

Чтобы обезопасить себя, можно сделать дополнительное специальное описание этой же функции для работы только со строковыми переменными:


Declare Function lreadString _
  Lib "kernel32" Alias "_lread" _
  (ByVal hFile As Long, ByVal lpBuffer As String, _
  ByVal wBytes As Long) As Long

При работе с этим описанием указывать ByVal при обращении уже не нужно:


wBytes = lreadString (hFile&, MyVarString, Len(MyVarString))

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

В начало статьи

Совет 29. Точно объявляйте способ передачи параметров

Обычно мы указываем способ передачи параметров — ByRef (по ссылке) или byVal (по значению) — только при работе с Win API (точнее со всеми DLL-функциями). В предыдущем совете мы показали, как это важно. При работе с процедурами внутри среды VB исторически было принято, что по умолчанию передача параметров всегда выполняется по ссылке. Но в VB.NET это правило изменено — теперь все параметры по умолчанию будут передавать по значению.

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

Но при переходе от VB 6.0 к VB.NET это изменение может вызвать неприятности:


 Dim a%, b%, c%
 a = 0: b = 1: c = 2
 Call MyTest(a, b, c)
 MsgBox a & " " & b & " " & c

Public Sub MyTest(ByVal d%, f%, ByRef g%)
  d = 10: f = 11: g = 12
End Sub

При работе в VB 6.0 будет получен результат


0 11 12

а в VB.NET:


0 0 12

Надеюсь что средства миграции обеспечат замену описания подпрограммы на


Public Sub MyTest(ByVal d%, ByVal f%, ByRef g%)

но лучше определять в явном виде возвращаемые параметры уже сейчас.

В начало статьи