Общие члены классов

Закрытые общие поля классов в сочетании со ReadOnly-свойствами очень удобны, но этим область применения ключевого слова Shared не исчерпывается. В классе можно объявлять общие свойства и методы. Как было показано на примере класса Math, при обращении к общим средствам класса указывается либо имя класса, либо имя конкретного экземпляра. Допустим, в класс Employee включается общая функция Calcul ateFICA, зависящая от двух открытых констант:

Public Const FICA_LIMIT As Integer = 76200

Public Const FICA_PERCENTAGE As Decimal = 0.062D

Функция CalculateFICA выглядит так:

Public Shared Function CalculateFICA(ByVal aSalary As Decimal) As Decimal

If aSalary > FICA_LIMIT Then

Return FICA_LIMIT * FICA_PERCENTAGE

Else

Return aSalary * FICA_PERCENTAGE

End If

End Function

Общие члены класса могут использоваться без создания экземпляров Empl oyee, только по имени класса. Пример:

System.Console.WriteLine(Employee.

CalculateFICA(100000))

С другой стороны, метод мджно вызвать и для конкретного экземпляра Employee:

System.Console.WriteLine

(Tom.CalculateFICA

(Tom.GetSalary())

Конструкторы тоже можно объявлять общими, для этого в объявление метода

New включается ключевое слово Shared. Общие конструкторы:

  • не обладают атрибутами Publiс или Private;
  • вызываются без параметров;
  • могут работать только с общими полями класса. Как правило, общие конструкторы применяются только для инициализации общих данных. Код общего конструктора выполняется при создании первого экземпляра указанного класса, перед вызовом всех остальных конструкторов.
  •  

    Жизненный цикл объекта

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

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

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

    Dim Tom As New EmployeeC'Tom ", 100000)

    Tom = New Employee("Tom ". 125000)

    В этом фрагменте создаются два разных объекта Empl oyee, причем после присваивания во второй строке первый объект Тот теряется. Иногда это соответствует намерениям программиста, иногда — нет. Например, если идентификатор работника хранится в общей переменной Empl oyeeID, то вторая строка присвоит второму объекту Тот идентификатор на 1 больше первоначального. Так или иначе, следующий фрагмент заведомо невозможен:

    Dim Tom As New Employee("Tom ", 100000)

    Dim Tom As New Employee("Tom ", 125000)

    Компилятор выдает следующее сообщение об ошибке:

    The local variable 'Tom' is defined multiple times in the same method.

     

    Уничтожение объектов

    В VB .NET объекты не умирают «естественной смертью»; в каком-то смысле они постепенно «уходят в небытие» со временем. Главное отличие от предыдущих версий VB заключается в том, что вы не можете явно освободить память, занимаемую объектом. Встроенный сборщик мусора когда-нибудь заметит, что эти блоки памяти не используются в программе, и автоматически освободит их. Автоматическая сборка мусора оказывает сильное влияние на программирование в VB .NET. В частности, сборку мусора следует рассматривать как полностью автоматизированный процесс, на который вы абсолютно не можете повлиять.

    Хотя в программе можно провести принудительную сборку мусора вызовом метода System. GC. Collect(), считается, что это не соответствует хорошему стилю программирования .NET. Мы рекомендуем всегда полагаться на автоматическую сборку мусора.

    Вспомните, что в прежних версиях VB в каждом классе существовало событие Termi nate, которое гарантированно вызывалось в тот момент, когда количество ссылок уменьшалось до 0 (в терминологии ООП это называется детерминированным завершением). В VB .NET (как бы вы к этому ни относились) поддерживается только недетерминированное завершение, из чего следует, что вы не можете рассчитывать на то, что некий аналог события Termi nate будет вызван в определенный момент времени. Более того, не гарантировано даже то, что он вообще будет когда-нибудь вызван!

    Некоторые программисты считают Finalize аналогом события Terminate в VB .NET, однако эта аналогия неверна. Метод Finalize всего лишь содержит код, который должен выполняться при освобождении памяти вашего объекта в процессе сборки мусора. Но поскольку вы не можете явно управлять тем, когда это произойдет, мы настоятельно рекомендуем использовать Finalize лишь как дополнительную меру безопасности — например, для дублирования метода Dispose, который должен вызываться пользователем класса. Метод Dispose рассматривается ниже.

    Возникает вопрос: если раньше мы проводили деинициализацию класса в событии Terminate, как же это делается сейчас? Для решения этой проблемы в VB .NET существует очень важное правило:

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

    Мы вернемся к методу Dispose при рассмотрении интерфейса IDisposabl e в главе 5. А пока достаточно сказать, что любое графическое приложение — даже самое простое, вроде продемонстрированного в главе 1, — относится к категории программ, в которых необходим метод Dispose. Это связано с тем, что графические программы захватывают так называемые графические контексты, которые должны быть освобождены для возвращения ресурсов в систему (графические контексты не являются блоками памяти, поэтому автоматическая сборка мусора в данном случае не поможет). Теперь становится ясно, почему в автоматически сгенерированный код, приведенный в главе 1, входит вызов Dispose. Недетерминированное завершение относится к числу самых неоднозначных нововведений .NET, однако автоматическая сборка мусора является неотъемлемой частью .NET. Разработчикам при всем желании не удалось бы сохранить прежний, детерминированный вариант управления памятью и обеспечить совместимость с .NET. Кроме того, механизму, использованному в старых версиях VB (подсчет ссылок), присущи проблемы с утечкой памяти, вызванной существованием циклических ссылок, когда объект А ссылается на объект В и наоборот, как показано на рис. 4.8.

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

    Рис. 4.8. Две разновидности циклических ссылок

     

    Структурные типы

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

    Ранние объектно-ориентированные языки пошли по самому прямолинейному пути. Скажем, в Smalltalk все данные интерпретировались как объекты. В результате такие языки работали медленнее, чем языки с разделением примитивных типов и объектов. С другой стороны, подобное разделение приводило к усложнению программ, поскольку программный код, работавший с числами, приходилось отделять от кода, работавшего с объектами. Чтобы интерпретировать число в объектном контексте, его приходилось «заворачивать» в объект. Например, в Java сохранение числа в эквиваленте динамического массива выглядело примерно так:

    anArrayList.Add(Integer(5));

    Число 5 «заворачивалось» в объект Integer. Такие программы плохо читались и медленно работали.

    В .NET Framework были объединены лучшие стороны обоих решений. В общем случае числа интерпретируются как примитивные типы, но при "необходимости они автоматически интерпретируются как объекты. Таким образом, для обычного числового литерала можно вызвать метод или занести его в хэш-таблицу без дополнительных усилий. Это называется автоматической упаковкой (boxing); обратный процесс называется распаковкой (unboxing).

    Для нас, программистов, из этого вытекает важное следствие: хотя в VB .NET все данные являются объектами, не каждая переменная в программе содержит манипулятор блока памяти и создается оператором New. Разумеется, ничто не дается даром: программисту приходится помнить о различиях между структурными и ссылочными типами. Первое, наиболее очевидное различие заключается в том, что новые экземпляры структурных типов создаются без ключевого слова New. Вам не придется (да и не удастся) использовать конструкции вида Dim a As New Integer(5).

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

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

    Чтобы узнать, обладает ли некий тип структурной семантикой, передайте переменную этого типа следующей функции.

    Function IsValueType(ByVal foo As Object)As Boolean

    If TypeOf (foo)Is System.ValueType Then

    Return True Else

    Return False

    End If

    End Function

    Для объектов структурного типа оператор Equal s всегда возвращает True, если структурные объекты содержат одинаковые данные. Синтаксис вызова выглядит так:

    a..Fquals(b)

    Учтите, что для ссылочных типов этот принцип в общем случае не выполняется. Например, два массива могут содержать одинаковые элементы, но равными при этом они не считаются.

    В VB .NET структурные типы делятся на две категории: структуры и перечисляемые типы. Мы начнем с перечисляемых типов, а затем перейдем к структурам, которые представляют собой «облегченные» варианты объектов.

     

    Перечисляемые типы

    Перечисляемые типы обычно используются для определения набора именованных целочисленных констант. При определении перечисляемого типа используется пара ключевых слов Enum-End Enum вместе с модификатором доступа. Перечисляемый тип может содержать только целочисленные типы вроде Integer или Long (тип Char недопустим). Например, в следующем фрагменте определяется открытый перечисляемый тип с именем BonusStructure:

    Public Enum BonusStructure

    None = 0

    FirstLevel = 1

    SecondLevel = 2

    End Enum

    После этого в любом месте программы можно объявить переменную типа BonusStructure: Dim bonusLevel As BonusStructure

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

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

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

    Bonus =Tom.Sales * bonusLevel.SecondLevel

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

    Public Function Calcu1ateBonus(ByVal theSales As Decimal) As Decimal

    Return theSales * BonusStructure.SecondLevel

    End Function

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

    BonusStructure.GetName(bonusLevel .GetType.l)

    Данный фрагмент выводит все имена, входящие в перечисляемый тип:

    Dim enumNames As String().s As String

    enumNames = BonusStructure.GetNames(bonusLevel.GetType)

    For Eachs In enumNames

    System.Console.WriteLine(s) Next