Определение классов в программе

От использования готовых классов .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 и атрибуты уровня доступа

Свойство Instancing VB6

Аналог в VB. NET

Private Класс объявляется с атрибутом Private
PublicNotCreatable Класс объявляется с атрибутом Public, но конструктор объявляется с атрибутом Friend
Singlellse и GlobalSingleUse Нет аналога в VB .NET
MultiUse И класс, и конструктор объявляются с атрибутом Public

 

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; оно обозначает конкретный объект, код которого выполняется в настоящий момент.