В главе 4 вы узнали, что в .NET проблема циклических ссылок решается отслеживанием всех объектов, используемых вашим приложением, и освобождением всех объектов, на которые отсутствуют ссылки, при помощи сборщика мусора.
В главе 5 было сказано, что все элементы данных вплоть до простейшей целой переменной представляют собой объекты.
Следовательно, все элементы данных, используемые в вашем приложении, подчиняются правилам управления памятью в .NET.
И это почти правда...
Ссылочные и структурные объекты
Разработчики .NET хорошо понимали, что было бы ужасно расточительно хранить в куче все данные до последней числовой переменной. Даже несмотря на то, что операции с кучей в .NET отличаются выдающейся эффективностью, в них неизбежно используется операция выделения памяти, которая выполняется значительно медленнее простого размещения данных в стеке. Не следует забывать и о затратах на отслеживание объектов и сборке мусора после освобождения.
Тем не менее разработчики .NET стремились к простоте и логической последовательности, обусловленной наследованием всех переменных от корневого типа Object.
Для разрешения этого конфликта им пришлось определить два типа объектов: ссылочные объекты и структурные объекты1.
Ссылочные (reference) объекты похожи на объекты СОМ, хорошо знакомые нам по VB6. Присваивание одного ссылочного объекта другому не сопровождается копированием данных: вместо этого вы просто получаете новую ссылку на существующие данные. Ссылочные объекты всегда размещаются в куче. Ссылочные объекты могут наследовать от других объектов, и другие объекты, в свою очередь, могут наследовать от них.
1 На всякий случай поясню, что термины «ссылочный тип» и «ссылочный объект» в данном контексте означают одно и то же (как и термины «структурный тип» и «структурный объект»).
Структурные (value) объекты размещаются в сегменте данных приложения или в стеке — не в куче. Структурные объекты VB .NET создаются практически так же, как и пользовательские типы в VB6, если не считать того, что для них используется ключевое слово Structure.
Пример простого структурного типа, определяемого в проекте ValueType:
Public Structure mystruct
Public AString As String
Public Sub SetString(ByVal newstring As String)
AString = newstring
End Sub
Public Sub New(ByVal InitialString As String)
AString = InitialString
End Sub
End Structure
В этом объявлении следует обратить внимание на ряд интересных обстоятельств.
Прежде всего, структура содержит строковое поле. В VB6 можно было определять строки фиксированного размера, хранящиеся в структуре. В VB .NET тип данных String является ссылочным1. Таким образом, у вас имеется структурный объект, который содержит переменную ссылочного типа. Так зачем же в этом случае использовать структурный тип?
Абсолютно незачем. Это неудачное решение.
Структурные типы лучше всего подходят для создания небольших объектов, в основном для числовых типов данных, например комплексных чисел или координат точек на плоскости.
В отличие от пользовательских типов VB6, структуры VB .NET могут обладать методами и свойствами. В них также могут определяться конструкторы — методы, предназначенные для инициализации структур. Впрочем, есть одна тонкость: определяемый вами конструктор должен получать хотя бы один параметр. Конструктор по умолчанию, используемый CLR, инициализирует все поля структуры нулями.
1 Строки более подробно описаны в главах 8 и 9 — в VB .NET они заметно отличаются от строк VB6. А пока достаточно помнить, что строка хранится в структуре в виде указателя на блок памяти в куче. Непосредственное содержимое строки в структуре отсутствует.
Метод Buttonl_Click проекта ValueType выглядит следующим образом:
Private Sub Buttonl_Click(8yVal sender As Object,
ByVal e As System.EventArgs) Handles buttonl.Click
Dim a As mystruct
a.SetString ("Hello")
Dim b As mystruct = a
Debug.WriteLine("B's String: " + b.AString)
Dim с As New mystruct("Another string")
Debug.Wri teLineC'C's String: " + c.AString)
Debug.WriteLine("C's ToString: " + c.ToString())
Dim obj As Object
obj = с
' Упаковка в объект
Debug.WriteLine(obj .ToString())
End Sub
В этом методе продемонстрированы три разных способа инициализации структуры mystruct.
Команда Dim a As mystruct создает структурную переменную а. Конструктор по умолчанию инициализирует все поля структуры нулями (строки инициализируются значением Nothing). Если бы тип mystruct в этой команде был ссылочным, переменная а была бы равна Nothing: ссылочные объекты должны создаваться перед использованием. Не существует такого понятия, как недействительная или пустая структурная переменная. Следовательно, объявления Dim a As mystruct и Dim a As New mystruct эквивалентны, если не считать того что во втором варианте могут использоваться другие конструкторы.
VB .NET позволяет инициализировать переменные синтаксической конструкцией вида:
Dim myvaluetype = начальное_значение
Таким образом, команда
Dim I As Integer = 5
является вполне допустимым способом объявить целую переменную и присвоить ей начальное значение 5.
Структуры одного типа; можно свободно присваивать друг другу, при этом VB .NET копирует данные структур на уровне полей. Таким образом, следующий фрагмент вполне допустим:
Dim a As mystruct
Dim b As mystruct
b = a
Такие конструкции можно сокращать до вида:
Dim a As mystruct
Dim b As mystruct = a
Последний объект с использует перегруженный1 метод New, чтобы создание и инициализация строки выполнялись одной командой: Dim с As New mystruct("Another string")
Как показывает следующий фрагмент, структурный объект может присваиваться ссылочной переменной:
Dim obj As Object ob] = с
Когда у CLR возникает необходимость интерпретировать структурный тип в ссылочном контексте, выполняется операция, называемая упаковкой (boxing): в куче создается временный объект, и в него копируются члены структурного типа. Обратный процесс называется распаковкой (unboxing).
1 Термин «перегрузка» означает наличие нескольких одноименных методов в классе или структуре. Перегруженные методы различаются только параметрами. Перегрузка подробно рассматривается в главе 10.
При выполнении обработчика события Buttonl_Click в окне отладки выводится следующий результат:
B's String: Hello
C's String: Another string
C's ToString: ValueType.mystruct
ValueType.mystruct
Этот пример демонстрирует копирование и инициализацию структур, а также тот факт, что в упакованном ссылочном объекте сохраняется информация о его типе.
Обработчик Button2_Click (листинг 6.1) еще нагляднее поясняет различия между структурными и ссылочными типами.
Листинг 6.1. Копирование объектов ссылочных и структурных типов1
Private
Public AString As String
Public Sub SetString(ByVal newstring As String)
AString = newstring
End Sub
Public Sub New(ByVal InitialString As String)
AString = InitialString
End Sub
End
Private Sub Button2_Click(ByVal sender As Object,
ByVal e As System.EventArgs) Handles button2.Click
Dim SI As New mystruct("Hello")
Dim 52 As mystruct
Dim cl As New myello")
Dim c2 As my
S2 = S1
c2 = cl
S2.SetString ("Modified")
c2.SetString ("Modified")
Debug.WriteLineC'Struct: " + SI.AString)
Debug.WriteLineC'; + cl.AString)
End Sub
Класс myпрактически идентичен структуре mystruct, и инициализация, казалось бы, происходит одинаковым образом. Что произойдет, если создать копии структуры и объекта, а затем модифицировать их? При нажатии кнопки в окне отладки выводятся значения свойства AString исходной структуры и объекта:
Struct: Hello
ied
Как видите, при модификации копии структуры изменяется только копия, а оригинал остается в прежнем состоянии. Присваивание ссылочной переменной означает всего лишь создание новой ссылки на исходный объект — не важно, какая из переменных будет использоваться для обращения к объекту для чтения или записи.
1 Все исходные тексты можно найти на сайте издательства «Питер» www.piter.com. — Примеч. ред.
Ниже перечислены некоторые обстоятельства, которые необходимо учитывать при выборе типа создаваемого объекта.
Наверное, вы уже догадались, что К структурам я отношусь без особого энтузиазма. На первый взгляд это может показаться странным, поскольку в VB6 пользовательские типы обладали рядом существенных преимуществ перед объектными типами. В частности, они могли содержать массивы фиксированного размера и фиксированные строковые данные, что позволяло организовать эффективную пересылку данных в виде больших блоков.
В структурах массивы и строки остаются объектами, создаваемыми в куче и находящимися под управлением CLR1. Куча устроена таким образом, что операции создания и освобождения объектов в ней выполняются очень быстро. При создании ссылочной переменной память просто выделяется из свободной области в верхней части кучи2. Освобождение ссылочной переменной сводится к простому присваиванию Nothing или ссылки на другой объект — куча в этом вообще не участвует. Конечно, замечательная скорость операций выделения и освобождения памяти не обходится бесплатно. Как вы узнали из главы 4, системе приходится периодически выполнять сборку мусора, чтобы освободить память от неиспользуемых объектов и перевести ее в свободную часть кучи.
Структурные типы лучше всего подходят для ситуаций, когда вы работаете с большим количеством мелких объектов, основанных на других структурных типах и не содержащих полей, относящихся к ссылочным типам.
1 Существуют средства, позволяющие более точно управлять расположением полей в структурах и определять в структурах строки и массивы фиксированной длины — в первую очередь, при вызовах функций API и передаче данных средствами СОМ. Об этом речь пойдет в части 3 этой книги.
2 Я немного упрощаю; в действительности CLR для повышения эффективности поддерживает раздельные пространства свободной памяти для малых и больших объектов.
В остальных случаях обычно лучше обходиться ссылочными типами. Иначе говоря, если вы привыкли создавать и использовать пользовательские типы вместо классов, пора менять привычки.
Среда CLR оптимизирована для ускоренного создания, освобождения и уничтожения мелких объектов. В частности, для этого отслеживается срок существования объектов. Каждый раз, когда объект «переживает» сборку мусора, он «стареет». Оказывается, в типичной программе действует правило: чем дольше существует объект, тем больше вероятность того, что он продолжит существование вплоть до завершения программы. Новые объекты часто относятся к категории временных или существуют в течение вызова одной функции. Когда возникает необходимость в сборке мусора, .NET сначала пытается ограничиться «нулевым поколением», то есть только новыми объектами. По информации Microsoft, во многих случаях мелкие временные объекты уничтожаются настолько быстро и эффективно, что они вообще не покидают процессорного кэша и не записываются в основную память.
Давайте разберемся, что же в действительности происходит при освобождении объекта в процессе сборки мусора.
На рис. 6.1 показана куча с пятью объектами. С двумя объектами (помеченными буквой F) ассоциируются завершители (то есть в программный код этих объектов входит метод Finalizer). CLR в сочетании с компилятором JIT опознает объекты, имеющие завершители, и отслеживает их особо.
Рис. 6.1. Куча, содержащая два объекта с завершителями
Когда сборщик мусора видит, что на объект с завершителем больше не существует ни одной ссылки, он не удаляет этот объект немедленно. Вместо этого объект заносится в отдельный список объектов, которые должны пройти стадию завершения (рис. 6.2).
Объекты остаются в списке завершения, пока отдельный фоновый программный поток не запустит завершитель. После запуска завершителя объект удаляется из списка завершения наконец, освобождается в следующем цикле работы сборщика мусора1.
1 Да, все верно — объекты с завершителями проходят сборку мусора дважды: при включении в список завершения и при фактическом освобождении памяти.
Рис. 6.2. Объекты с завершителями не уничтожаются при сборке мусора
Сколько времени проходит от исчезновения последней ссылки на объект до выполнения завершителя?
Неизвестно.
Более того, текущая документация .NET даже не гарантирует, что завершители всегда будут выполнены в конце работы приложения, хотя на практике это все же происходит.
Не много найдется областей, которые бы лучше демонстрировали различия между VB .NET и VB6, чем проблемы завершения.
Программисты VB6, изучавшие теорию объектно-ориентированного программирования, знают «правильный» способ инициализации и деинициализации компонентов. В обработчике события Initialize инициализируются переменные класса и даже выделяются необходимые системные ресурсы. В обработчике события Terminate выполняются необходимые завершающие действия. Событие Terminate происходит сразу же после освобождения последней ссылки на объект, поэтому в общем случае можно считать, что в обработчике Terminate все объектные переменные сохраняют действительные значенитя1. Вы можете твердо рассчитывать на то, что событие Terminate наступит и даст вам возможность «прибрать» за приложением в момент прекращения его работы (освободить системные ресурсы, закрыть файлы и т. д.).
В VB .NET эти правила не действуют2.
Чтобы понять, почему это происходит, достаточно рассмотреть пример Undeadl. В этом проекте определен класс Undying:
Public /font>
Protected Overrides Sub Finalize()
Debug.WriteLineC'I have been finalized")
End Sub
End
На форме находится кнопка cmdCreate, которая создает и удаляет объект:
Private Sub buttonl_Click(ByVal sender As System.Object_
ByVal e As System.EventArgs) Handles buttonl.Click
' Внимание: в отличие от VB6 следующая команда создает объект.
Dim obj As New Undying()
End Sub
В VB .NET в отличие от VB6 команда New немедленно создает объект. В VB6 команда New просто сообщает VB о том, что объект должен быть автоматически создан при первом обращении к его свойствам или методам.
Это изменение влияет и на освобождение объектных переменных. В VB6 при объявлении переменной в команде New следующего вида:
Dim myObject As New SomeObjectType
объект myObject создавался заново при каждой ссылке на него в программе. Если присвоить переменной myObject значение Nothing, а затем снова сослаться на него, будет использован новый экземпляр объекта SomeObjectType.
В VB .NET объект SomeObjectType создается непосредственно при выполнении команды Dim. Если затем присвоить myObject значение Nothing и попытаться сослаться на него, возникает ошибка времени выполнения.
Теперь попробуйте запустить приложение и нажмите кнопку. После некоторого количества нажатий будет выполнена сборка мусора с вызовом завершителей (правда, для этого может потребоваться тысяча нажатий, а то и больше). Закройте приложение. При окончательной сборке мусора будут выполнены все завершители.
1 При выходе из приложений VB6 объекты освобождаются в произвольном порядке. Следовательно, во время обработки события класса Terminate ссылки на объекты могут стать недействительными, что иногда даже приводит к ошибкам защиты.
2 И не обманывайте себя мыслью, будто вы избежите всех затруднений переходом на С#. В СП имеются деструкторы, похожие на деструкторы C++, однако работают они точно так же, как и завершители VB .NET.
Промежуток между освобождением и завершением кажется незначительным, но в действительности он играет довольно важную роль. Если ваши объекты захватывают ограниченные ресурсы (например, подключения к базе данных или сокеты), то временная недоступность ресурсов для этих освобожденных, но еще не уничтоженных объектов может отрицательно сказаться на быстродействии и масштабируемости приложения.
Недетерминированное завершение: трудный выбор
Visual Basic 6 (и объекты СОМ вообще) обеспечивает детерминированное завершение: вы всегда знаете, что завершители будут вызваны и когда именно это произойдет. Как видно из предыдущего обсуждения, в CLR детерминированное завершение не поддерживается. Нельзя точно предсказать, когда будет вызван завершитель, а в некоторых случаях вызов завершителя вообще не гарантирован.
Некоторые программисты VB осуждают эти изменения и ругают Microsoft за то, что в VB .NET не поддерживается детерминированное завершение.
Честно говоря, я совершенно не представляю, как Microsoft могла сохранить эту возможность. Вспомните: одним из главных аргументов в пользу сборки мусора был отказ от подсчета ссылок, часто приводящего к утечке памяти и возникновению циклических ссылок. Все, что мне приходит в голову, — либо выполнять сборку мусора после каждой модификации объектной переменной, либо вернуться к подсчету ссылок. Первый вариант совершенно неприемлем из-за катастрофического снижения быстродействия, а при возврате к подсчету ссылок снова возникнут проблемы с утечкой памяти и циклическими ссылками.
Лично я считаю, что преимущества сборки мусора компенсируют отказ от детерминированного завершения.
С другой стороны, эти изменения неизбежно отразятся на процессе программирования объектов в VB .NET.
Инициализация в целом почти не изменилась. Если уж и говорить о каких-то изменениях, то это изменения к лучшему, потому что теперь при конструировании объекта можно передавать параметры. Впрочем, отсутствие детерминированного завершения наложило свой отпечаток — выделение ресурсов при инициализации следует свести к минимуму и выделять ресурсы лишь перед непосредственным использованием.
Вместо того чтобы дожидаться вызова завершителя для освобождения ресурсов, в .NET рекомендуется использовать другой подход: когда надобность в объекте отпадает, работающий с ним клиент требует выполнить все необходимые действия по деинициализации и освобождению ресурсов. По общепринятым правилам для этой цели обычно используется метод Dispose. Деинициализация в методе Dispose позволяет отказаться от вызова завершителей (в результате из процесса сборки мусора исключается лишняя стадия, что улучшает быстродействие программы). Также часто встречается комбинированное решение: метод Dispose освобождает ресурсы по запросу клиента, а завершитель выполняет окончательную деинициализацию в том случае, если метод не вызывался.
Одна из возможных реализаций использована в приложении InitAndDestract. В этом примере класс Component1 объявляется производным от класса System. ComponentModel .Component. Класс Component может использовать дизайнер компонентов, обеспечивающий включение других компонентов. Класс System. ComponentModel .Component также наследует интерфейс IDisposable, в котором определяется метод Dispose. Реализация этого метода по умолчанию вызывает методы Dispose внутренних компонентов, что помогает автоматизировать процесс деинициализации.
В классе Component1 продемонстрированы следующие возможности:
Определение компонента приведено в листинге 6.2 (код конструктора частично опущен).
Листинг 6.2. Пример InitAndDestruct
' Инициализация и уничтожение
' Copyright ® 2001 by Desaware Inc.
Public tl
Inherits System.ComponentModel.Component
Shared Sub New()
MsgBox ("Shared component initializer called")
End Sub
Public Sub New()
MyBase.New()
' Следующий вызов необходим для дизайнера компонентов 1 InitializeComponent()
' Дальнейшая инициализация выполняется после
' InitializeComponent()
MsgBox ("My component instance created")
End Sub
Protected Overrides Sub Finalize()
MsgBox ("My component finalizer called")
End Sub '
Public Overloads Overrides Sub Dispose()
MyBase.Dispose()
MsgBox ("I've been disposed")
End Sub
End
Для тестирования компонента используется обработчик события Click, который создает и удаляет объект:
Private Sub buttonl_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonl.Click
Dim obj As New Componentl()
' Завершив работу с объектом, вызовите метод Dispose (см. текст)
obj .Dispose()
End Sub
При первом нажатии кнопки сначала вызывается конструктор класса, затем конструктор объекта, а затем метод Dispose (который должен явно вызываться клиентом). При следующих нажатиях кнопки вызывается только конструктор объекта и метод Dispose. Завершитель вызывается при освобождении объекта.
Клиент должен помнить о необходимости вызова Dispose. К счастью, во многих случаях .NET решает эту задачу за вас. Например, элемент, размещаемый на форме, включается в коллекцию Controls формы. Когда форма выполняет собственную деинициализацию (это происходит в тот момент, когда среда вызывает метод Dispose для формы), она, в свою очередь, вызывает методы Dispose всех элементов в коллекции Controls. Тем не менее среда ничего не знает о компонентах, создаваемых в программе (таких, как компонент Component1 в рассмотренном примере). Для таких объектов вы должны вызвать Dispose самостоятельно.
Заставлять контейнер явно вызывать методы Dispose его объектов — далеко не идеальное решение. Впрочем, с учетом архитектуры .NET вряд ли можно придумать что-нибудь лучшее,; К1не кажется, что преимущества от деинициализации объектов средой CLR перевешивают недостатки — но, признаюсь, мне все же не хватает детерминированного завершения.
Ниже перечислены некоторые рекомендации, относящиеся к завершению объектов.
1 Теоретически элемент управления можно создать в программе и не использовать его. Для таких элементов автоматический вызов метода Dispose не поддерживается, и вам придется вызывать Di spose вручную.
Сценарий, о котором я собираюсь поведать, выглядит совершенно фантастически. Несомненно, он полностью противоречит опыту практически любого программиста VB.
Рассмотрим следующую последовательность событий.
Вот так сюрприз! Объект, который уже был освобожден и подготовлен к уничтожению, снова стал действительным! Иначе говоря, «мертвый» объект вернулся к жизни.
Рассмотрим класс Resurrected из проекта Resurrection.
Public ted
Public mycontainer As Forml
Public Sub AreYouThereO
HsgBox ("I am here") End Sub
Protected Overrides Sub FinalizeO
MsgBox ("I'm being finalized")
mycontainer.PriorObject = Me
End Sub
End
Открытому свойству mycontainer присваивается ссылка на форму-контейнер. Также имеется метод с именем AreYouThere, который просто доказывает, что объект еще существует. У формы имеется открытое свойство с именем PriorObject, содержащее ссылку на объект Resurrected. Циклическая ссылка (форма ссылается на объект, а объект ссылается на форму) в VB .NET не вызывает проблем. Как только форма перестает ссылаться на объект, он будет передан сборщику мусора.
Помимо свойства PriorObject в форме определены четыре события для четырех кнопок: Create & Delete, ForceGC, Check Prior Object, Turn Finalizer Back On.
Листинг 6.3. Модуль формы приложения Resurrection
Public PriorObject As Resurrected
Private Sub cmdRef1nal1ze_CHck(ByVal sender As Object,
ByVal e As System.EventArgs) Handles crodReflnalize.Click
If Not PriorObject Is Nothing Then
GC.ReReg1sterForF1nalize (PriorObject)
End If
PriorObject = Nothing
End Sub
Private Sub cmdPr1or_CHck(ByVal sender As Object,
ByVal e As System.EventArgs) Handles cmdPMor.Click
If Not PriorObject Is Nothing Then
' Доказательство того, что завершенный объект ]
' все еще существует! PriorObject.AreYouThere()
End If
End Sub
Private Sub cmdCreate_CUck(ByVal sender As Object,
ByVal e As System.EventArgs) Handles cmdCreate.Click
Dim obj As New ResurrectedO
' Объект получает ссылку на форму,
obj.mycontainer = Me
End Sub
Private Sub cmdGC_Click(ByVal sender As Object.
ByVal e As System.EventArgs) Handles cmdGC.Click
' Выполнить принудительную сборку мусора.
GC.Collect()
GC.WaitForPendingFinalizersO
End Sub
End
За «воскрешением» объектов можно понаблюдать на примере проекта Resurrection (см. листинг 6.3).
Выполните следующие действия.
1. Нажмите кнопку Create & Destroy — приложение создает объект, присваивает его свойству my container ссылку на форму и освобождает объект (при выходе из функции форма уже не содержит переменную со ссылкой на объект).
2. Нажмите кнопку Force GC, форсирующую немедленную сборку мусора и завершение объектов. В процессе завершения объект использует свое свойство mycontainer и создает в свойстве PriorObject формы ссылку на себя. Другими словами, на объект снова существует ссылка в переменной Form (корневого уровня).
3. Нажмите кнопку Check Prior Object — как видите, объект, для которого был вызван завершитель, продолжает существовать!
4. Закройте приложение. Хотя завершители должны вызываться при выходе из приложения, вы увидите, что завершитель нашего объекта не вызывается.
Как такое возможно? Выполненный завершитель не выполняется снова, даже если объект «воскрес»!
Снова запустите программу Resurrection, повторите действия 1-3 и нажмите кнопку Turn Finalizer Back On; обработчик этой кнопки тоже присваивает переменной Pri orObj ect значение Nothing. Снова нажмите кнопку Force GC — вы увидите, что завершитель выполняется повторно. Метод GC.ReRegisterForFinalize сообщает сборщику мусора о том, что объект вернулся к жизни и его завершитель должен быть снова выполнен при повторной сборке мусора1.
На первый взгляд кажется, будто я трачу много времени на описание проблемы, которая никогда не возникнет ни у одного здравомыслящего программиста. В каком-то смысле это верно: подобные ситуации гораздо чаще возникают в результате ошибок, нежели в результате сознательных решений проектировщика.
1 При закрытии приложения окно сообщения не выводится: умный VB .NET понимает, что приложение завершает работу, и не выводит окна сообщений в это время.
Однако понимание этого процесса очень важно для овладения одной важной методикой, которой можно и нужно пользоваться.
Наряду с методом ReRegisterForFinalize существует метод SuppressFinalize, при помощи которого вы сообщаете сборщику мусора о том, что объект не должен проходить завершение, даже если у него имеется завершитель. На этом приеме основана одна важная оптимизация. Выше я советовал деинициализировать объекты в методе Dispose и использовать метод-завершитель на тот случай, если клиент не вызовет Dispose. Вызывая SuppressFinalize в методе Dispose, вы предотвратите ненужную деинициализацию.
Управление памятью в .NET не только имеет далеко идущие последствия для языка VB .NET, но и значительно влияет на общий подход к проектированию приложений.
В начале этой главы вы узнали о различиях между структурными и ссылочными типами и о том, что преимущества пользовательских типов VB6 не распространяются на структурный тип VB .NET. Другими словами, в VB .NET классам следует отдавать предпочтение перед структурами практически во всех случаях, кроме небольших структур, состоящих только из других структурных типов.
Кроме того, вы узнали, что за дополнительные преимущества в надежности и масштабируемости (устранение утечек памяти и циклических ссылок) приходится расплачиваться отсутствием в CLR детерминированного завершения.
Из этого следует, что программисты VB .NET должны избегать освобождения ресурсов в методе Finalize. Вместо этого следует унаследовать интерфейс IDisposable1 и реализовать метод Di spose. Метод-завершитель должен использоваться как последнее средство для выполнения деинициализации в ситуациях, когда метод Dispose не вызывался. При вызове Dispose для повышения быстродействия можно запретить вызов завершителя методом GC.SupressFinalize.
Наконец, вы узнали, что «мертвые» объекты могут вернуться к жизни, если из-за ошибок объектной модели на стадии завершения на объект будет создана ссылка.
Темы глав 5 и 6 — наследование и управление памятью — представляют два из трех крупных концептуальных изменений, с которыми сталкиваются программисты при переходе с VB6 на VB .NET. Именно эти новшества вызовут немало проблем у программистов VB6, не желающих расстаться со старыми навыками программирования или слепо хватающихся за новые разрекламированные возможности языка. В главе 7 рассматривается третье из этих изменений — многопоточность.
1 Или объявить класс производным от класса, наследующего интерфейс IDisposable.
Инфо
|