Cамоучитель по VB.NET



 

Хэш-таблицы


Простые и динамические массивы удобны прежде всего тем, что вы можете напрямую обратиться к любому элементу по индексу. Конечно, для этого необходимо знать индекс. В следующей структуре данных — хэш-таблице — произвольный доступ к данным осуществляется по ключу. Допустим, у вас имеется хэш-таблица с именем theData. Команда theData("Bill 's Address") позволяет извлечь из хэш-таблицы нужный элемент без циклического перебора всего содержимого. Хэш-таблицы очень удобны в ситуациях, когда вы хотите получить быстрый доступ к значению по связанному с ним уникальному атрибуту, то есть ключу. Разумеется, программирование хэш-таблицы — задача непростая [ Для этого необходимо построить хорошую функцию хэширования для вычисления индекса данных по ключу, а также решить неизбежную проблему коллизий, то есть совпадения хэш-кодов у двух разных элементов. Даже терминология выглядит устрашающе... ], но, к счастью, эта работа уже выполнена за вас разработчиками .NET Framework.

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

В табл. 4.4 перечислены важнейшие методы класса Hashtable (за полным списком обращайтесь к электронной документации).

Методы класса HashTable учитывают регистр символов в строковых ключах, и действие команды Option Compare Text на них не распространяется. О том, как создать хэш-таб-лицу, игнорирующую регистр символов, рассказано в главе 5.

Таблица 4.4. Важнейшие методы класса Hashtable

Имя

Описание

Add

Добавляет новую пару «ключ/значение» в хэш-таблицу

Clear

Удаляет из хэш-таблицы все содержимое

ContainsKey

Проверяет, содержит ли хэш-таблица заданный ключ (с учетом регистра символов)

ContainsValue СоруТо

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

Копирует элементы хэш-таблицы в массив

Count

Возвращает количество пар «ключ/значение» в хэш-таблице

Item

 

Свойство по умолчанию. Получает или задает значение, связанное с указанным ключом

Keys

Возвращает все ключи хэш-таблицы в виде коллекции, содержимое которой перебирается в цикле For-Each

Remove

Удаляет из хэш-таблицы значение с заданным ключом

Values

Возвращает все значения хэш-таблицы в виде коллекции, содержимое которой перебирается в цикле For-Each

При помощи класса 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

Рис. 4.7. Результат работы тестовой программы

Происходящее выглядит по меньшей мере странно; мы передаем массив по значению, но изменения почему-то отражаются в исходной копии! В предыдущих версиях 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), к числу которых относятся обычные числа, даты и перечисляемые типы (программист также может определять собственные структурные типы, как будет показано далее в этой главе). Для структурных типов передача по значению работает вполне традиционно. Странная ситуация, описанная выше, возникает только при передаче по значению изменяемых ссылочных типов.

 

Назад Начало Вперед