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


При использовании кода, написанного другими программистами, всегда возникает потенциальная опасность конфликтов имен. Но даже если забыть об этих конфликтах, все равно необходимо каким-то образом сгруппировать тысячи методов в соответствии с их функциональным назначением. Следовательно, в огромной библиотеке классов .NET должен существовать какой-либо способ логической группировки информации. В .NET эта задача решается при помощи пространств имен. Конечно, вы можете создавать собственные пространства имен; ниже в этой главе мы покажем, как это делается.
В каждом городе есть своя Главная Улица, а в каждой библиотеке непременно найдется метод с именем Open. Пространства имен позволяют различать эти методы. Например, в пространстве имен System. 10 собраны методы для выполнения файловых операций; в него входит класс Fil е, а в этом классе имеется метод Open. Полное имя метода выглядит так:
System.I0.File.Open
Класс File является частью пространства имен System. I0, поэтому он не конфликтует с другим классом File из пространства имен Cornell .Morrison.NiftyClasses, который также может содержать собственный метод Open.
 
Импортирование
Обращаясь к хорошим знакомым, мы не называем их по имени-отчеству и фамилии. В .NET предусмотрена возможность импортирования пространств имен командой Imports, что позволяет избавиться от громоздких полных имен. После правильной команды Imports все ссылки вида
System.Text.StringBuilder .
могут заменяться простым именем StringBuilder.
Пространство имен System автоматически импортируется в каждое решение, созданное в Visual Studio. Благодаря этому методы этого пространства имен могут вызываться в виде Console.WriteLine() вместо полного имени System.Console.WrlteLlne().
Список пространств имен, автоматически импортируемых в решение, находится на странице Imports окна свойств решения ( 2).
Загрузите в Object Brewser автоматически импортируемое пространство имен Microsoft. Visual Basic — вы увидите, что оно содержит различные функции, существовавшие в Visual Basic и сохраненные в VB .NET ( 3).
Импортирование пространства имен Microsoft. Visual Basic. Constants позволяет использовать старые константы VB — такие, как vbCrtf. .NET-версии многих констант не имеют префикса vb (например, CrLf) и находятся в пространстве имен
Microsoft.VIsualBasi с.Control Chars.
Команда Imports должна располагаться перед всеми остальными объявлениями, включая объявление имени модуля, но после директив Option (таких, как Option Strict On или Option Compare Text).
Команда Imports не увеличивает объем программы, поскольку она не включает весь код пространства имен в проект, а просто упрощает ссылки на члены классов, входящих в пространство имен. На скорость работы она тоже не влияет. Импортируются только пространства имен, входящие в сборки, ссылки на которые были включены в программу командой Project > Add Reference (сборки рассматриваются в главе 13).
Мы были просто поражены, когда узнали, что подсказка IntelliSense работает и для команды Imports — она выводит список пространств имен, которые можно импортировать в проект. Для этого IDE анализирует сборки, ссылки на которые имеются в решении.
Если два импортированных пространства имен содержат классы с одинаковыми именами, то для различения этих классов вам придется указывать их полные имена (VB .NET не позволяет создать два одноименных класса в одном пространстве имен).
Особая версия команды Imports используется для предотвращения потенциальных конфликтов имен с ранее импортированными классами. Например, если вы захотите работать на уровне совместимости с прежними версиями Visual Basic (чего делать не рекомендуется), в программе почти наверняка возникнут конфликты имен. Включите в программу ссылку на уровень совместимости VB, а затем воспользуйтесь командой вида:
Imports VB6Stuff = Microsoft.VisualBasic.Compatibility.VB6
После этого остается лишь начинать все ссылки на уровень совместимости с префикса «VBSStuff.», и все проблемы с конфликтами имен исчезнут.
Из пространства имен нельзя импортировать отдельный класс, чтобы упростить ввод имен его членов. Предположим, вы хотите импортировать класс DirectoryInfo, чтобы упростить ввод имен его членов. Но следующая команда недопустима:
Imports System.IO.Directorylnfo
 
Класс DirectoryInfo
Чтобы рассмотреть пример использования Imports на сколько-нибудь нетривиальном примере, мы возьмем класс Directorylnfo из пространства имен System. IO. Как подсказывает само название, класс Directorylnfo содержит методы для получения информации о содержимом каталогов, вывода полного имени каталога и т. д. Один из конструкторов этого класса получает строку с именем каталога, который вы хотите проанализировать (если переданное имя не является абсолютным, конструктор считает, что оно задается относительно текущего каталога программы). Правильно написанная команда Imports позволяет заменить длинную команду Dim dirlnfo As New System.IO.Directory!nfo("C:\") более компактной и понятной командой
Dim dirlnfo As New DirectoryInfo("C:\")
Следующая программа выводит список всех каталогов на жестком диске, в ее работе используются рекурсия и класс Directorylnfo. Ключевую роль в ней играет метод GetDi rectories (), возвращающий коллекцию подкаталогов. Функция ListDi rectories перебирает содержимое коллекции и рекурсивно вызывается для каждого элемента:
Option Strict On Imports System.IO Module Modulel
Sub Main())
Dim dirlnfo As New DirectoryInfo("C:\")
ListDirectories(dirInfo)
End Sub
Sub ListDirectories(ByVal theDirectory
As Directorylnfo)
Dim tempDir*As DirectoryInfo
Console. Wri.teLi net theDi rectory .Full Name())
For Each terrain In theDi rectory. GetDi rectories ()
ListDirectories(tempOir) Next End Sub End Module
Если вы привыкли к рекурсивному перебору каталогов с использованием старой функции Dir, вы оцените, насколько упростил эту программу в .NET замечательный метод Directorylnfo.
Во время работы над этим примером мы легкомысленно назвали свое решение D1-rectorylnfo. В результате команда Imports перестала работать! Причины так и остались неизвестными, но мораль ясна: не присваивайте своим решениям имена, совпадающие с именами классов библиотеки .NET.
 
Справочная система и .NET Framework
В библиотеку .NET Framework входят сотни пространств имен, каждое из которых содержит множество полезных классов. По масштабам и возможностям .NET Framework сравнима с полным интерфейсом Win32 API. Библиотека настолько огромна, что описать ее в одной книге попросту невозможно. Хотя эта глава дает начальное представление о некоторых классах .NET Framework, как можно скорее приступайте к чтению документации .NET. Начните с раздела «.NET Framework Class Library» и найдите описания пространств имен, представляющих для вас интерес. Как показано на  4, в справочной системе перечислены все классы каждого пространства имен.
Каждое имя класса в левом столбце представляет собой гиперссылку, ведущую к подробному описанию класса. В нижней части описания класса перечислены имена его членов. Если щелкнуть на любом из этих имен, вы перейдете к подробному описанию соответствующего члена. Обратите внимание: VB .NET уже не считается второстепенным языком — синтаксис всех членов приводится для VB, VC и С#. На  5 показан пример документации класса Directorylnfo в бета-версии 2.
Чтобы получить подробное описание метода GetDi rectories, использованного в предыдущем примере, щелкните на ссылке Directorylnfo в нижней части страницы, а затем щелкните на ссылке GetDi rectories. Внешний вид страницы показан на  6. Некоторые термины, встречающиеся на этой странице (такие, как Pri vate), рассматриваются далее в этой главе,
 
Классы коллекций в .NET Framework
Чтобы пробудить в вас интерес к .NET Framework, мы кратко рассмотрим некоторые классы коллекций. В этих классах реализуются стандартные структуры данных, часто используемые в нетривиальных программах. Коллекции настолько важны, что они по умолчанию автоматически импортируются в каждое решение VB .NET (в пространстве имен System.Collections).
В совокупности эти классы оставляют далеко позади примитивный класс Collection из VB6. Самые полезные классы коллекций перечислены в табл. 4.2. В следующих двух разделах рассматриваются основные принципы работы с двумя важнейшими классами: ArrayList и HashTable. Знакомство с очень важным классом Dictio-naryBase откладывается до следующей главы, посвященной наследованию.
Таблица 4.2. Основные классы коллекций

 
ArrayList
Класс ArrayList реализует динамический массив, размеры которого автоматически увеличиваются и уменьшаются по мере надобности. Динамические массивы работают чуть медленнее обычных массивов, но они заметно упрощают многие задачи программирования. Кроме того, в отличие от большинства массивов класс ArrayLi st является гетерогенным, то есть позволяет хранить объекты разных типов. В главе 5 будет показано, как создать класс ArrayList для хранения объектов лишь одного типа; вы также узнаете о некоторых нюансах, связанных с хранением обобщенных объектов в ArrayLi St.
Использование ArrayList вместо базового массива означает, что вам не придется часто вызывать ReDim Preserve для сохранения существующих данных. Достаточно вызвать метод Add, и класс ArrayList сам выполнит всю черновую работу. Класс ArrayList содержит ряд других полезных методов. Например, метод AddRange позволяет перенести в динамический массив все содержимое существующего массива всего одной командой. После завершения обработки элементы можно скопировать обратно. В частности, это позволяет легко объединить содержимое двух массивов. В табл. 4.3 перечислены основные члены класса ArrayList (полный список приведен в электронной документации).
Таблица 4.3. Важнейшие члены класса ArrayList

Среди свойств класса ArrayList наибольший интерес представляет свойство Item, которое представляет элемент с заданным индексом. Пример:
Consolе.WriteLinediiyList.Item( 1))
Свойство Item является свойством по умолчанию класса ArrayList. Это означает, что при использовании его имя может не указываться, Например, приведенная выше команда эквивалентна следующей команде:
Console. WriteLine(myList(1))
В разделе «Свойства» настоящей главы вы узнаете, чем отличаются свойства по умолчанию в VB .NET и прежних версиях VB.
В следующем коротком примере массив ArrayLi st используется для ввода и сохранения неизвестного количества строк. При этом удается обойтись без команды ReDim Preserve, необходимой при работе с обычными массивами.
Option Strict On Module Modulel
Sub Main()
Dim myList As New ArrayList()
Dim theData As String
Console.Write("Please enter each item and hit Enter key,"_
& "enter ZZZ when done:") theData =Console.ReadLine()
Do Until theData ="ZZZ" myList.Add(theData)
Console.WriteC'Please enter each item and hit Enter,"_
& "enter ZZZ when done:") theData =Console.ReadLine() Loop
Console.WriteLine("You entered "SmyList.Count() & "ITEMS.")
Console.ReadLine()
End Sub
End Module
Хэш-таблицы
Простые и динамические массивы удобны прежде всего тем, что вы можете напрямую обратиться к любому элементу по индексу. Конечно, для этого необходимо знать индекс. В следующей структуре данных — хэш-таблице — произвольный доступ к данным осуществляется по ключу. Допустим, у вас имеется хэш-таблица с именем theData. Команда theData("Bill 's Address") позволяет извлечь из хэш-таблицы нужный элемент без циклического перебора всего содержимого. Хэш-таблицы очень удобны в ситуациях, когда вы хотите получить быстрый доступ к значению по связанному с ним уникальному атрибуту, то есть ключу. Разумеется, программирование хэш-таблицы — задача непростая [ Для этого необходимо построить хорошую функцию хэширования для вычисления индекса данных по ключу, а также решить неизбежную проблему коллизий, то есть совпадения хэш-кодов у двух разных элементов. Даже терминология выглядит устрашающе... ], но, к счастью, эта работа уже выполнена за вас разработчиками .NET Framework.
Другую категорию структур данных, предназначенных для выборки значения по ключу, составляют ассоциативные массивы (словари). Они часто реализуются в виде хэш-таблиц с дополнительным кодом для выполнения особых операций (например, обнаружения повторяющихся значений или ключей).
В табл. 4.4 перечислены важнейшие методы класса Hashtable (за полным списком обращайтесь к электронной документации).
Методы класса HashTable учитывают регистр символов в строковых ключах, и действие команды Option Compare Text на них не распространяется. О том, как создать хэш-таб-лицу, игнорирующую регистр символов, рассказано в главе 5.
Таблица 4.4. Важнейшие методы класса Hashtable

При помощи класса Hashtable можно сохранить информацию, полученную при вызове метода GetEnvironmentVariables класса System. Environment. Приведенная ниже небольшая программа выводит имена и значения всех переменных окружения, определенных в системе. Программу можно завершить в любой момент, просто закрыв консольное окно. Сначала просмотрите листинг, а потом мы объясним пару неочевидных моментов:
1 Option Strict On
2 Imports System.Environment
3 Module Modulel
4 Sub Main()
5 Dim eVariables As Hashtable
6 eVariables =CType(GetEnvironmentVariables().
Hashtable)
7 Console.Writel_ine("Press Enter to
see the next item")
8 Dim thing As Object
9 For Each thing In eVariables.Keys
10 Console.WriteLineC'The environment
variable named " & _
11 thing. ToString() & "has value " &
eVariables(thing).ToString())
12 Console. ReadLine()
13 Next
14 End Sub
15 End Module
Прежде всего использованный в строке 6 упрощенный синтаксис имени метода стал возможным благодаря вызову Imports в строке 2: eVariables =CType(GetEnvironmentVariables(),Hashtable)
Значение, полученное при вызове GetEnvironmentVariables(), преобразуется в хэш-таблицу функцией СТуре [ Возможно, в будущих иерсиях .NET такое преобразование работать не будет. ]. В строках 8 и 9 для перебора элементов хэш-таблицы используется переменная типа Object:
Dim thing As Object
For Each thing In eVariables.Keys
В стандартных хэш-таблицах хранятся только объекты. Но поскольку в VB .NET все данные являются объектными, строковые значения переменных окружения также могут сохраняться в переменной thing. Программа перебирает содержимое коллекции Keys и при помощи свойства Item для каждого ключа получает ассоциированное значение. Конструкцию eVariables(thing) в строке 11 также можно записать в следующем виде:
eVariables.Item(thing)
В строке 11 вызывается метод ToString, определенный в каждом классе (этот важный метод описан в главе 5). Здесь этот метод используется для вывода строкового представления ключа.
 
Объектные переменные
Рассмотрим следующий фрагмент:
Dim thing As New Object
Dim aRandomlnstance As New Random
В нем объявляются и создаются две переменные: thing и aRandomlnstance. Первая переменная содержит ссылку на тип Object, а вторая — ссылку на экземпляр класса Random. Следующая команда вполне допустима даже в режиме жесткой проверки типов (Option Strict On), поскольку в VB .NET все переменные в конечном счете представляют собой объекты:
thing = aRandomlnstance
С другой стороны, обратное присваивание (aRandomlnstance = thing) недопустимо, поскольку не каждый объект является экземпляром класса Random.
Объектную переменную можно рассматривать как манипулятор блока памяти (причем не фиксированного, а перемещаемого). Объектные переменные также часто называют ссылками (references) или интеллектуальными указателями (smart pointers). Обычно при использовании знака = с ключевым словом New манипулятор связывается с блоком памяти, в котором хранится соответствующий объект (при работе с так называемыми структурными типами возникают некоторые тонкости, которые будут рассматриваться далее в этой главе).
Как будет показано в следующей главе, общим предком всех типов VB .NET является тип Object. Именно поэтому в VB .NET любую величину можно сохранить в переменной типа Object, а любой созданный объект поддерживает методы класса Object. Например, поскольку в классе Object определен метод ToString, каждый класс позволяет получить строковое представление объекта (полезность которого зависит от реализации). Метод ToString автоматически вызывается при использовании конструкций вида Console. WriteLine(foo).
Если объектная переменная содержит манипулятор блока памяти, в результате операции присваивания второй объектной переменной будет присвоен манипулятор того же блока памяти. Но если вы забудете о том, что для работы с одним блоком памяти используются две разные переменные, это может привести к печальным последствиям — изменения в состоянии объекта, внесенные через одну переменную, автоматически повлияют на другую переменную. Для примера рассмотрим следующий фрагмент:
Sub Maln()
Dim A As New ArrayList()
Dim В As ArrayList
В = А
B.Add("foo")
Console.WriteLine(A.Count)
Console.ReadLine() End Sub
Динамический массив А также будет содержать строку foo, поэтому выведенное значение A.Count будет равно 1.
Если вы знакомы с языками, в которых широко используются указатели (например, С или Pascal), вы увидите, что у объектных переменных есть много общего с указателями. Главное различие состоит в том, что разыменование (dereferencing) объектных переменных происходит автоматически и с ними не могут выполняться математические операции.
Поскольку в VB .NET строки и массивы являются объектами, следует помнить, что для работы с ними используются объектные переменные. Как было показано в главе 3, это позволяет использовать встроенные возможности соответствующих классов при помощи синтаксиса «.». Например, при работе с массивом через переменную апАггау команда anArray.Sort() отсортирует массив чрезвычайно эффективным методом быстрой сортировки.
К сожалению, за все хорошее приходится платить. Передача объектных переменных по значению связана с определенными трудностями, которые теперь распространяются и на стандартные объекты вроде массивов. Данная тема рассматривается в. разделе «Проблемы с передачей объектных переменных по значению» этой главы.
Как и в прежних версиях VB, объектные переменные могут использоваться для получения более компактной записи. Например, в следующем фрагменте определяется короткое имя аВох, которое будет использоваться вместо длинного Му-
Form.TextBoxl:
Dim aBox As System.Windows.Forms.TextBox aBox = MyForm.TextBoxl
Подобные сокращения часто используются в сочетании с ключевым словом With:
With aBox
.AutoSize =False
.Height =1000
.Width =200
.Text ="Hello"
End With
 
Is и Nothing
Оператор Is проверяет, ссылаются ли две объектные переменные на одну область памяти. Следующий фрагмент в обоих случаях выводит True, поскольку в результате операций присваивания все объектные переменные ссылаются на одну область памяти:
Dim Objectl As New Object()
Dim ObjectZ As New Object()
Dim Objects As New Object()
ObjectZ =Object1
Objects Object2
Console.WriteLine(Objectl Is Object2)
Console.WriteLine(Object1 Is Object3)
Как и в прежних версиях VB, присваивание объектной переменной значения Nothi ng разрывает ее связь с блоком памяти. Когда объектная переменная равна Nothing, она не ассоциируется ни с каким объектом. В этом состоянии находятся все объектные переменные, которые были объявлены в программе, но еще не инициализировались. В программе часто встречаются проверки следующего вида:
If anObject Is Nothing Then
' Переменная не связана с объектом, присвоить значение
Else
' Значение было присвоено ранее
End If
Дополнительная информация о том, что происходит при присваивании объектным переменным значения Nothing, приведена в разделе «Сборка мусора и завершение».
 
TypeName и TypeOf

Переменные, объявленные с типом Object, могут использоваться для хранения произвольных объектов. Следовательно, программисту необходимы средства для определения типа объекта, связанного с объектной переменной. В VB .NET эта задача решается двумя способами: функцией TypeName и оператором TypeOf ...Is.
Функция TypeName возвращает строку с описанием типа. Для всех типов, кроме базовых, должен быть предварительно вызван оператор New; в противном случае функция возвращает строку Nothing. Например, следующий фрагмент выводит в консольном окне строку Nothing:
Dim anSBuilder As System.Text.StringBuilder
Console.WriteLineC'My type name is " & TypeName(anSBuilder))
Но после вызова New в окне будет выведена строка StringBuilder:
Dim anSBuilder As New System.Text.StringBuilder
Console.WriteLineC'My type name is " & TypeName(anSBuilder))
Функция TypeName возвращает короткое имя класса, поэтому не рассчитывайте получить полное имя вида System.Text.StringBuilder.
Если вызвать функцию TypeName для массива, вы получите строковое имя, за которым следует пустая пара круглых скобок. Пример:
Dim aThing(5)As Integer
Console.WriteLine("My type Harness " & TypeName(aThing))
Полученная строка имеет вид Integer().
Функция TypeName удобна в процессе отладки, но в окончательных версиях программ обычно используется оператор TypeOf...Is. Он работает гораздо эффективнее, поскольку обходится без сравнений строк, необходимых при использовании TypeName. Синтаксис проверки выглядит следующим образом:
If TypeOf aThing Is System.Text.SthngBuilder Then
' Объект относится к типу StringBuilder End If
Оператор TypeOf...Is возвращает True, если объект относится к заданному типу или является производным от него. Поскольку в .NET все объекты являются производными от общего предка Object проверка вида TypeOf...Is Object всегда возвращает True, даже если переменная относится к типу, производному от Object. Если вам потребуется узнать точный тип объектной переменной, воспользуйтесь методом GetType.
 
Проблемы с передачей объектных переменных по значению
Большинство языков программирования требует четкого понимания, чем передача параметров по ссылке отличается от передачи по значению. Не забывайте, что в VB .NET параметры по умолчанию передаются по значению (ByVal).

Большинство программистов руководствуется простым правилом: если параметр передавался по ссылке, его изменения сохраняются в исходной переменной, а если по значению — изменения теряются после выхода из функции или процедуры. К сожалению, в случае с объектными переменными это правило не всегда истинно. Попробуйте выполнить следующий фрагмент, в котором массив передается в процедуру по значению. Вы убедитесь в том, что исходный массив изменяется после вызова процедуры!
Module Modulel Sub Main()
Dim a() As String ={"HELLO"."GOODBYE"}
Console.WriteLineC'Original first item in array is:" & a(0))
Console.WriteLineC'Original second item in array is:" & a(1))
Yikes(a) ' Массив передается по значению!
Console.WriteLineC'After passing by value first item in array now is:"_
&A(0))
Console.WriteLine("After passing by value second item in array is:"_
&АШ)
Console. ReadLine()
End Sub
Sub Yikes(ByVal Foo As String())
Foo(0) = "GOODBYE"
Food) = "HELLO"
End Sub
End Module
Происходящее выглядит по меньшей мере странно; мы передаем массив по значению, но изменения почему-то отражаются в исходной копии! В предыдущих версиях VB это было бы невозможно. Итак, что происходит?
Главная причина заключается в том, что при передаче по значению всегда создается новая копия исходной переменной; после выхода из функции эта копия уничтожается. Но, передавая по значению объектную переменную, вы приказываете VB .NET создать копию манипулятора для работы с объектом. Внутри процедуры операции с временным манипулятором отражаются на содержимом этой области памяти. После вызова из процедуры копия уничтожается, но все изменения в содержимом памяти остаются в силе.
Представьте себе чемодан, к которому временно приделали вторую ручку. Вы перенесли чемодан за новую ручку на другое место; даже если теперь отсоединить ручку, чемодан все равно останется на новом месте.
В этой странной ситуации есть лишь одно исключение — когда исходный объект является неизменяемым (immutable). Из стандартных, постоянно используемых классов к этой категории относится только класс Stri ng. В этом случае передача по значению работает именно так, как положено, в чем нетрудно убедиться при помощи следующей программы:
Option Strict On Module Modulel Sub Main()
Dim A As String = "hello"
NoProblem(A)
Console.WriteLine("After passing by value the string is still " & A)
Console. ReadLine()
End Sub
Sub NoProblem(ByVal Foo As String)
Foo = "goodbye"
End Sub
End Module
BVB .NET существуют так называемые структурные типы (value types), к числу которых относятся обычные числа, даты и перечисляемые типы (программист также может определять собственные структурные типы, как будет показано далее в этой главе). Для структурных типов передача по значению работает вполне традиционно. Странная ситуация, описанная выше, возникает только при передаче по значению изменяемых ссылочных типов.
 
Определение классов в программе
От использования готовых классов .NET Framework мы переходим к определению собственных классов в программе. Код класса можно разместить в отдельном файле при помощи команды Project > Add Class, как в VB6, или же просто ввести его в нужном модуле — например, в стартовом модуле, содержащем точку входа в консольное приложение.
В процессе тестирования мы предпочитаем связывать каждый класс с процедурой Sub Main, в которой он используется. Таким образом, код классов не оформляется в виде отдельных модулей классов, а выделяется в программный модуль с отдельной процедурой Sub Main, предназначенной для их тестирования. Если вы последуете нашему примеру, учтите, что код, определяемый на уровне модуля, доступен везде, где доступен сам модуль. Таким образом, мы создаем некий аналог глобальных переменных и функций VB .NET — со всеми опасностями, присущими глобальным данным.
VB .NET не смотрит на то, сколько классов определяется в одном файле. В большинстве классов определяются один или два конструктора, свойства для чтения и изменения состояния объекта, а также методы для выполняемых действий. Для примера возьмем простейший класс Empl oyee с двумя полями (имя и зарплата) и небольшую тестовую программу. В классе определяются два свойства, доступных только для чтения; эти свойства возвращают значения полей. Методы в этом классе отсутствуют:
1 Module EmployeeTestl
2 Sub Main()
3 Dim Tom As New Employee("Tom". 100000)
4 Console.WriteLine(Tom.TheName & "salary is " & Tom.Salary)
5 Console. ReadLine()
6 End Sub
7 ' Определение класса
8 Public Class Employee
9 Private m_Name As String
10 Private m_Salary As Decimal
11 Public Sub New(ByVa1 sName As String.ByVal curSalary As Decimal)
12 m_Name = Sname
13 m_Salary = curSalary
14 End Sub
15 Public Readonly Property TheName()As String
16 Get
17 Return m_Name
18 End Get
19 End Property
20 Public Readonly Property Salary() As Decimal
21 .Get .
22 Return m_Salary
23 End Get
24 End Property
25 End Class
26 End Module
В строках 2—6 определяется процедура Sub Main, используемая компилятором в качестве точки входа. Если эта процедура выбрана в качестве стартового объекта (это происходит по умолчанию, но вообще стартовый объект выбирается в диалоговом окне Project Properties), она отвечает за создание исходных экземпляров. Далее созданные объекты обычно создают другие объекты в ответ на получение ими сообщений. Конечно, в нашей простой программе ничего такого не происходит.
Непосредственное создание объекта происходит в строке 3, играющей ключевую роль в процессе тестирования программы. В этой строке при создании нового объекта Empl oyee методу New передаются два параметра — имя и начальная зарплата. В строке 4 мы выводим значения свойств TheName и Salагу, чтобы убедиться в том, что исходное состояние созданного объекта было задано верно.
Класс Empl oyee определяется в строках 8-25. Как упоминалось выше, для удобства тестирования код класса определяется в исходном модуле, хотя мы с таким же успехом могли воспользоваться командой Project > Add Class и выделить его в отдельный файл.
Давайте внимательно рассмотрим каждую строку в определении класса. В строке 8 ключевое слово Publiс является атрибутом уровня доступа и определяет, кому разрешено создавать экземпляры этого класса. В нашем примере класс объявлен открытым, поэтому теоретически любой желающий сможет создавать его экземпляры после компиляции — для этого в программу достаточно включить ссылку на сборку, содержащую этот класс (сборки рассматриваются в главе 13). Чтобы класс мог использоваться только в рамках нашего проекта и оставался недоступным для внешних программ, ключевое слово Public следует заменить ключевым словом Friend.
В строках 9 и 10 определяются закрытые поля для хранения информации о состоянии объекта. В очередной раз напомним, что переменные всегда должны объявляться закрытыми (Private). В своих определениях классов и модулей мы всегда начинаем имена полей с префикса m_ или m.
В строках 11-14 определяется конструктор, предназначенный для создания экземпляров класса. Конструктор задает значения закрытых полей экземпляра в соответствии со значениями полученных параметров.
В строках 15-19 и 20-24 определяются два открытых свойства, доступных только для чтения. Они предназначены для получения информации о текущем состоянии объекта. В приведенном примере использовано ключевое слово Return, однако с таким же успехом можно было применить старый синтаксис с присваиванием имени свойства:
Get
TheName = m_Name
End Get
Впрочем, даже в этой форме синтаксис процедуры свойства несколько изменился по сравнению с VB6 — исчезли старые конструкции Property Get/Property Set.
Следующая версия программы показывает, как сделать свойство Salary доступным для чтения и записи. Для этого достаточно удалить ключевое слово Readonly и добавить небольшой фрагмент:
Public Property Salary()As Decimal Get
Return m_Salary End Get Set(ByVal Value As Decimal)
m_Salary = Value
End Set
End Property
В этом фрагменте прежде всего следует обратить внимание на чтение нового значения свойства с применением ключевого слова Value. Иначе говоря, когда в программе встречается строка вида Tom.Salary = 125000, параметру Value автоматически присваивается значение 125 000.
Иногда встречаются ситуации, когда свойство требуется объявить доступным только для записи. Для этого перед именем свойства ставится ключевое слово WriteOnly, a затем определяется секция Set без секции Get.
Допустим, мы решили объявить свойство Salary доступным только для чтения и включить в класс метод для повышения зарплаты. Метод объявляется как обычная процедура или функция. В нашем примере метод не возвращает значения, поэтому выбор стоит остановить на процедуре:
Public Sub RaiseSalary(ByVal Percent As Decimal)
m_Salary =(1 + Percent) * m_salary
End Sub
Члены класса объявляются с модификаторами Public, Private или Friend. Ключевое слово Pri vate означает, что член класса используется только внутри класса.
В классах иногда объявляются закрытые конструкторы. «Какая польза от закрытого конструктора?» — спросите вы. Конструктор объявляется закрытым в том случае, если он вызывается только самим классом исходя из логики его работы.
По умолчанию в VB .NET для классов и их членов используется уровень доступа Friend (доступ разрешается только из текущей программы). Впрочем, пропускать атрибуты уровня доступа при объявлении класса не рекомендуется — особенно если учесть, что уровень доступа, принятый по умолчанию, не является минимальным.
 
Атрибуты уровня доступа и создание объектов
Атрибуты уровня доступа, установленные для класса, управляют возможностью создания объектов соответствующего типа. Грубо говоря, они являются отдаленным аналогом свойства Instancing в VB6, хотя для некоторых значений Instancing приходится дополнительно учитывать уровень доступа конструктора. В табл. 4.5 описано соответствие между свойством Instancing VB6 и комбинациями атрибутов уровня доступа класса и конструктора.
Таблица 4.5. Значения свойства Instancing и атрибуты уровня доступа

 
Me
Если класс используется как шаблон для создания однотипных объектов, программист должен иметь возможность сослаться на текущий объект, которому принадлежит выполняемый код. Зарезервированное слово Me всегда интерпретируется как объектная переменная, обозначающая текущий экземпляр. Применение Me гарантирует, что неоднозначная конструкция будет интерпретирована в контексте текущего класса.
Также стоит заметить, что один из самых распространенных (и самых нелепых) примеров использования Me встречается в ситуациях вроде следующей:
Public Class Point
Private x As Integer
Private у As Integer
Public Sub New(ByVal x As Integer.ByVal у As Integer)
Me.x = x
Me.у = у End Sub
' И т.д.
End Class
Запись Me. x используется для того, чтобы отличить поле х экземпляра от параметра х, передаваемого при вызове метода New. Конечно, проблема легко решается добавлением префикса m_ перед именем переменной класса, однако подобные конструкции часто используются в С#; возможно, они встретятся в программе, сопровождение которой вам будет поручено.
 
Перегрузка членов класса
Метод RaiseSalary класса Employee можно сделать и поинтереснее. Предположим, повышения зарплаты до 10% происходят автоматически, но для больших сумм требуется специальный пароль. В прежних версиях VB такие задачи решались при помощи необязательных параметров. Хотя эта возможность сохранилась и в VB .NET, существует более изящное решение с определением двух версий RaiseSalary. Используя возможность перегрузки методов, мы определяем два разных метода для разных случаев.
В VB .NET синтаксис перегрузки методов очень прост: для этого в программе просто определяются два метода с одинаковыми именами и разными параметрами. Тем не менее мы настоятельно рекомендуем использовать ключевое слово Over! oads. По нему пользователи вашего кода узнают о том, что метод перегружается намеренно, а не в результате ошибки. В следующем фрагменте приведены две версии метода RaiseSalary, о которых говорилось выше:
Public Overloads Sub RaiseSalary(ByVal Percent As Decimal)
If Percent > 0.1 Then
' Операция запрещена - необходим пароль
Console.WhteLineC'MUST HAVE PASSWORD TO RAISE SALARY " & _
"MORE THAN 10*!!!!") Else X
m_Salary =(1 + Percent) * m_salary End If End Sub
Public Overloads
Sub RaiseSalary(ByVal Percent As Decimal._
ByVal Password As Stqng)
If Password -"special Then
m_Salary = (1 + Percent) * m_Salary
End If End Sub
При перегрузке методы класса различаются только по типам параметров. Методы не могут перегружаться по типу возвращаемого значения или уровню доступа.
Ниже приведен пример класса Empl oyee с перегруженным методом Rai seSalany, а также небольшая тестовая программа. Обратите внимание: 10%-ный порог не кодируется в программе, а определяется в виде константы:
Option Strict On Module Modulel Sub Main()
Dim Tom As New Employee("Tom". 100000)
Console.WhteLineCTom.TheName & " has salary " & Tom.Salary)
Tom.RaiseSalary(0.2D)
' Суффикс D - признак типа Decimal
Console.WriteLine(Tom.TheName & " still has salary " & Tom.Salary)
Console. WhteLine()
Dim Sally As New Employee("Sally", 150000)
Console.WriteLine(Sally.TheName & " has salary " & Sally.Salary)
Sally.RaiseSalary(0.2D,"special")
' Суффикс D - признак типа Decimal
Console.WriteLine(Sally.TheName & "has salary "SSally.Salary)
Console. WriteLine()
Console.WriteLine("Please press the Enter key")
Console. ReadLine()
End Sub
End Module
Public Class Employee
Private m_Name As String
Private m_Salary As Decimal
Private Const LIMIT As Decimal = 0.1D
Public Sub New(ByVal theName As String,ByVal curSalary As Decimal)
m_Name = thename
m_Salary = curSalary
End Sub
Readonly Property TheName()As String Get
Return m_Name
End Get '
End Property
Readonly Property Salary()As Decimal Get
Return m_Salary
End Get
End Property
Public Overloads
Sub RaiseSalary(ByVal Percent As Decimal)
If Percent > LIMIT Then
' Операция запрещена - необходим пароль
Console.WriteLine("MUST HAVE PASSWORD TO RAISE SALARY " & _
"MORE THAN LIMIT!!!!")
Else
m_Salary =(1 +Percent)*m_salary End If End Sub
Public Overloads
Sub RaiseSalary(ByVal Percent As Decimal._
ByVal Password As String)
If Password = "special" Then
m_Salary =(1 + Percent) * m_Salary
End If
End Sub
End Class
 
Снова о конструкторах
Если в вашем классе не определен конструктор, VB .NET автоматически генерирует для него конструктор, вызываемый без аргументов. Работа этого конструктора сводится к инициализации всех полей экземпляра значениями по умолчанию. Такой конструктор называется конструктором по умолчанию или безаргумеитнъм конструктором. Если в классе определен хотя бы один пользовательский конструктор, VB .NET не станет генерировать конструктор по умолчанию.
Ничто не мешает вам определить в классе несколько конструкторов с разными уровнями доступа. Например, можно определить абсолютно безопасный конструктор с атрибутом Public и конструктор с атрибутом Friend, использование которого сопряжено с чуть большим риском. Конечно, эти конструкторы должны вызываться с разными параметрами, поскольку VB .NET различает методы по списку параметров, а не по модификаторам уровня доступа.
Конструкторы перегружаются, как и остальные методы, однако при этом нельзя использовать ключевое слово Overloads. Ниже приведен фрагмент обновленной версии класса Employee с конструктором, позволяющим задать значение нового поля.
Public Class Employee
Private m_Name As String
Private m_NickName As String
Private m_Salary As Decimal
Public Sub NewCByVal sName As String.ByVal curSalary As Decimal)
m_Name = sName
m_Salary = curSalary
End Sub
Public SubNewCByVal theName As String.ByVal nickName As String._
ByVal curSalary As Decimal)
m_Name = theName
m_NickName = nickName
m_Salary = curSalary
End Sub
Компилятор выбирает вторую версию конструктора лишь в том случае, если при вызове передаются два строковых параметра и один числовой. При передаче одной строки и числа выбирается первый конструктор.
Перегрузка конструкторов приводит к дублированию кода в программе. Так, в приведенном выше фрагменте значения m_Name и fli_Sa,lary присваивались в обоих конструкторах. В VB .NET для таких ситуаций предусмотрена специальная сокращенная запись: конструкция MyClass.New вызывает другой конструктор класса [ На момент написания книги также можно было воспользоваться ключевым словом Me, но вариант с MyClass является предпочтительным. ]. Пример:
Public Sub New(ByVal sName As String.ByVal curSalary As Decimal)
m_Name = Sname
mJSalary = curSalary End Sub
Public Sub New(ByVal sName As String, ByVal nickName As String._ ByVal curSalary As Decimal)
MyClass.Newt sName.curSalary)
m_NickName =nickName
End Sub
При вызове другого конструктора конструкцией MyClass. New порядок определения конструкторов в программе не важен. VB .NET выбирает конструктор по типу переданных параметров независимо от его места в определении класса.
Помните, что MyClass — ключевое слово, а не объект. Значение MyCLass нельзя присвоить переменной, передать процедуре или использовать в операторе Is. В подобных ситуациях используется ключевое слово Me; оно обозначает конкретный объект, код которого выполняется в настоящий момент.

 

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