Сайт Алексея Муртазина (Star Cat) E-mail: starcat-rus@yandex.ru
Мои программы Новости сайта Мои идеи Мои стихи Форум Об авторе Мой ЖЖ
VB коды Статьи о VB6 API функции Самоучитель по VB.NET
Собрания сочинений Обмен ссылками Все работы с фото и видео
О моём деде Муртазине ГР Картинная галерея «Дыхание души»
Звёздный Кот

Самоучитель по VB.NET
Глава 5.

Наследование

Когда информация о .NET только начинала распространяться, я часто видел, как докладчики из Microsoft стояли перед программистами Visual Basic и перечисляли новые возможности того, что когда-то называлось VB7. Одной из таких возможностей, неизменно вызывавшей громкое одобрение аудитории, было наследование.

Я никогда не мог понять, почему это происходит.

Впрочем, у меня есть предположение. Думаю, программисты Visual Basic ком-плексовали перед своими коллегами, работавшими на C++, которые часто задирали носы и говорили: «Visual Basic — т настоящий объектно-ориентированный язык. В настоящем объектно-ориентированном языке есть полноценное наследование». Услышав это, бедный программист VB удалялся в свою каморку и за час выдавал больше кода, чем программист C++ мог написать за целую неделю. Но никто не обращал на это внимания, потому что C++ был «современным, элегантным, профессиональным объектно-ориентированным языком», a VB считался «пережитком прошлого, игрушкой, языком новичков» для тех, кто не мог освоить C++.

Так почему бы программистам VB не порадоваться?

Поделюсь с вами маленьким секретом.

Я программировал на C++ в течение большего срока, чем на Visual Basic, — и продолжаю активно программировать на обоих языках. Я стал убежденным сторонником объектно-ориентированного программирования в тот самый момент, когда впервые осознал концепцию класса в 1977 году. Я программировал в таких библиотеках, как ATL, в которых широко и успешно использовалось наследование.

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

Да, .NET использует наследование — оно встроено в саму архитектуру. Да, на базе наследования создаются иерархии классов, при помощи которых вы строите собственные программы.

Но если вы действительно понимаете суть наследования, возможно, вам удастся успешно прожить, не создав ни одного класса или компонента, который будет использоваться в механизме наследования.

В этой главе я постараюсь обосновать свою точку зрения.

 

«Повторное использование кода» — мантра программиста

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

Здорово, не правда ли?

Однако наследование — не единственный способ повторного использования кода.

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

У каждого из этих подходов есть свои достоинства и недостатки.

Чтобы вы лучше поняли все «плюсы» и «минусы», для начала я покажу пример плохого кода. В этой главе плохой код встречается сплошь и рядом.

На первый взгляд это смотрится довольно странно: читатель обычно рассчитывает найти в книге примеры хорошего кода и научиться тому, как правильно решить ту или иную задачу. Думаю, не менее важно взглянуть и на оборотную сторону медали; примеры плохого кода помогут вам избегать неверных решений1!

В примере, который мы рассмотрим, используется простой связанный список — чрезвычайно полезная структура данных для работы со списками объектов. В общем случае связанный список эффективнее массива, поскольку операции вставки и удаления объектов выполняются без сдвига элементов массива. Связанные списки бывают удобнее коллекций, поскольку в них проще управлять порядком объектов и переставлять их по мере необходимости.

Общая идея связанного списка проста: в каждом объекте хранится указатель (ссылка) на следующий объект в списке. Корневая переменная указывает на первый объект.

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

Связанный список в VB6

Начнем с построения класса VB6 для работы со связанным списком. Да, я помню, что эта книга посвящена VB .NET, но поверьте — самый простой способ действительно изучить и понять наследование основан на сравнении с включением и наследованием интерфейсов, реализованным в VB6. В проекте LinkListVBG представлен класс, упрощающий реализацию функциональности связанных списков в другом классе. Другими словами, наша цель — сделать так, чтобы этот код можно было использовать заново с минимальными хлопотами.

Объект LinkList обладает следующими свойствами и методами:

В Visual Basic 6 повторное использование кода обеспечивалось посредством включения (containment). Класс, объекты которого связывались в список, содержал экземпляр класса LinkList и вызывал его методы для выполнения необходимых операций.

Но к какому типу объекта относятся свойства Nextltem и Previousltem? К объекту LinkList или к тому объекту, который содержит объект LinkList?

На рис. 5.1 показан первый вариант, при котором объекты LinkList ссылаются на другой объект LinkList. В этом случае необходимы средства для получения ссылки на объект-контейнер (в нашем примере это гипотетический объект Customer).

Рис. 5.1. Объекты LinkList ссылаются на другие объекты LinkList. Для получения ссылки на контейнер используется свойство Container

В другом варианте, показанном на рис. 5.2, объект LinkList ссылается на объект-контейнер, содержащий внутренний объект LinkList.

Рис. 5.2. Объект LinkList ссылается на объект-контейнер Customer

Начнем со второго подхода, реализованного в проекте LinkListVBG. Класс LinkList не только рассчитан на включение в контейнер, но и обеспечивает интерфейс, через который контейнер выполняет операции со связанным списком. В контейнере используется команда Implements, обеспечивающая наследование интерфейсов. Это позволяет быстро реализовать в нужном объекте поддержку связанных списков.

Объект LinkList содержит две закрытые (Private) переменные. В переменной m_Next хранится ссылка на следующий объект списка. Объект LinkList указывает на контейнер, но поскольку контейнер реализует интерфейс LinkList, переменная m_Next может относиться к типу LinkList. В переменной m_Container хранится ссылка на объект-контейнер (вскоре вы поймете, зачем это нужно). Определение объекта LinkList выглядит так:

' LinkList VB6 пример #1

' Copyright ©2000 by Desaware Inc. All Rights Reserved

Option Explicit

' Данная версия демонстрирует внедрение объектов,

' поэтому для связи используется тип LinkList

Private m_Next As LinkList

' Ссылка на контейнер необходима

' для включения нового элемента в список

Private m_Container As Object

' Контейнер задается в процессе инициализации

1 Если вы захотите оформить класс в виде компонента,

' потребуется объявление с атрибутом Public

Friend Property Set Container(ByVal ContainerObject As Object)

Set m_Container = ContainerObject

 End Property

Свойство Nextltem просто возвращает ссылку на переменную m_Next, которая содержит ссылку на следующий объект в списке:

' Просто вернуть ссылку на следующий объект в списке. 

Public Property Get NextltemO As LinkList

Set Nextltem = m_Next 

End Property

Public Property Set NextItem(ByVal nextptr As LinkList)

Set m_Next = nextptr 

End Property

Co свойством Previousltem дело обстоит сложнее. Поскольку мы реализуем односвязный список, при запросе этого свойства необходимо перейти в начало списка и, последовательно перебирая все элементы, найти в нем текущий объект (точнее, объект, у которого свойство Nextltem совпадает с текущим объектом). Сложность заключается в том, как задать объект для сравнения. Сравнивать с Me нельзя, поскольку Me относится к внутреннему объекту LinkList, а не к тому объекту, на который ссылается переменная m_Next. Однако в сравнении должны участвовать объекты-контейнеры, поэтому в каждый объект LinkList приходится включать ссылку на контейнер:

' В односвязном списке метод свойства Previous 

' должен провести поиск от начала списка

Public Property Get PreviousItem(Root As LinkList) As LinkList 

Dim currentitem As LinkList

' Помните: все ссылки относятся к объекту-контейнеру!

 If (Root Is m_Container) Or (Root Is Nothing) Then,

Exit Property

End If r Set currentitem = Root 

Do

If currentitem.Nextltem Is m_Container Then 

Set Previousltem = currentitem Exit Property 

Else

Set currentitem = currentitem.Nextltem 

End If

Loop While Not currentitem Is Nothing 

End Property

Метод Remove при помощи свойства Previousltem находит в списке элемент, предшествующий удаляемому. Если такой элемент существует, его переменной m_Next присваивается значение m_Next текущего объекта, что фактически приводит к исключению текущего объекта из списка. Если удаляемый объект находится в первой позиции списка, то ссылка на следующий объект в списке присваивается переменной Root (передаваемой по ссылке). Пусть вас не смущает то обстоятельство, что в присваивании участвует переменная типа LinkList. В действительности Root присваивается ссылка на контейнер, реализующий интерфейс LinkList, а не на внутренний объект:

' Метод Remove находит предыдущий элемент списка

' последовательным перебором.

' Переменная Root должна передаваться по ссылке: это позволяет

' присвоить ей новое значение, если удаляемый объект

1 находится в начале списка.

Public Sub Remove(Root As LinkList)

Dim previtem As LinkList

Set previtem = PreviousItem(Root)

If previtem Is Nothing Then Set Root = m_Next

Else Set previtem.Nextltem = m_Next

End If 

End Sub

Метод Append также использует свойство m_Container для правильного включения объекта в список. Он находит последний объект в списке и присваивает его переменной m_Next ссылку на контейнер текущего объекта:

' Метод Append последовательно перебирает элементы 

' от Root до конЦа списка. 

Public Sub Append(Root As LinkList)

 Dim currentitem As LinkList

 Set currentitem = Root 

If Root Is Nothing Then

 Set Root = m_Container 

Else While Not currentitem.Nextltem Is Nothing

Set currentitem = currentitem.Nextltem 

Wend

Set currentitem.Nextltem = m_Container

 End If

 End Sub

Класс контейнера определяется в файле Customer.cls. В нем имеется открытое (Public) свойство CustomerName, предназначенное для получения имени контейнера. Класс реализует LinkList, тем самым наследуя интерфейс для работы со связанными списками. Он создает закрытый объект LinkList с именем m_MyLinkList, инициализируемый в обработчике события ze. Как видно из листинга 5.1, все реализованные функции просто вызывают соответствующие функции объекта LinkList.

Листинг 5.1. Класс Customer.cls проекта LinkListVB61

' LinkList VB6 пример #1

' Copyright ©2000 by Desaware Inc. All Rights Reserved

Option Explicit

' Эта версия "реализует" LinkList, чтобы работать с его методами 

' через объекты LinkList Implements LinkList

Public CustomerName As String

' Функциональность обеспечивается внутренним объектом LinkList

 Private m_MyLinkList As LinkList

Private Sub ize()

Set m_MyLinkList = New LinkList

Set m_MyLinkList.Container = Me

 End Sub

' He вызывается никогда. О том, как решить эту проблему, 

' будет рассказано ниже.

 Private Sub e()

Debug.Print "Terminating customer " & CustomerName 

End Sub

' Методы и свойства просто отображаются на соответствующие

' методы и свойства LinkList

Private Sub LinkList_Append(Root As LinkList)

m_MyLinkList.Append Root

 End Sub

Private Property Set LinkList_NextItem(ByVal nextobject As LinkList)

Set m_MyLinkList.Nextltem = nextobject

 End Property

Private Property Get LinkList_NextItem() As LinkList

Set LinkList_NextItem = m_MyLinkList.Nextltem 

End Property

Private Property Get LinkList_PreviousItem(Root As LinkList) As LinkList

Set LinkList_PreviousItem = m_MyLinkList.PreviousItem(Root) 

End Property

Private Sub LinkList_Remove(Root As LinkList)

m_MyLinkList.Remove Root 

End Sub

1Все исходные тексты можно найти на сайте издательства «Питер» www.piter.com. — Примеч. ред.

На форме TestForm.frm находятся: текстовое поле для ввода имени клиента, кнопка для включения нового клиента в перечень, список с именами клиентов и кнопка для удаления текущей выбранной строки из списка. Модуль содержит переменную m_List, в которой хранится ссылка на первый объект в списке. Хотя переменная m_List относится к типу LinkList, она, как и прежде, ссылается на объект-контейнер, а не на внутренний объект LinkList:

' LinkList VB6 пример #1

' Copyright ©2000 by Desaware Inc. AU Rights Reserved

Option Explicit

Dim m_List As LinkList

VB6 позволяет работать со свойствами и методами объекта при помощи переменной, представляющей этот интерфейс. Для получения списка объектов нам понадобятся две переменные: объект LinkList для перебора элементов (свойство Nextltem) и объект Customer для получения имени клиента (свойство CustomerName). Этот принцип — каждый интерфейс обладает собственным набором свойств и каждый объект может поддерживать несколько интерфейсов — играет очень важную роль в СОМ. Присваивание переменной одного типа значения другого типа (как, например, в приведенной ниже строке Set currentcustomer = currentlist) сопровождается вызовом метода Querylnterfасе, используемого СОМ для перехода между интерфейсами объекта. Вскоре вы поймете, почему я подчеркиваю это обстоятельство. Код формы Testfrm.frm выглядит следующим образом:

' Обновление списка

' Обратите внимание на использование разных переменных 

' для работы с интерфейсами 

Private Sub UpdateList() 

lstCustomers.Clear 

Dim currentlist As LinkList 

Dim currentcustomer As Customer 

Set currentlist - m_List 

Do While Not currentlist Is Nothing

 Set currentcustomer = currentlist 

IstCustomers.Addltem currentcustomer.CustomerName

 Set currentlist = currentlist.Nextlten 

Loop 

End Sub

Как показано в листинге 5.2, в функциях cmdAdd_Click и cmdRemove_Click для работы с объектами также используются переменные двух типов.

Листинг 5.2. Вставка и удаление элементов списка

Dim newEntry As New Customer

 Dim newEntryll As LinkList

newEntry.CustomerName = txtCustomerName.Text 

Set newEntryll = newEntry 

newEntryll.Append m_List 

UpdateList 

End Sub

Private Sub cmdRemove_Click()

Dim currentlist As LinkUst

 Dim currentcustomer As Customer

Set currentlist = m_List 

Do While Not currentlist Is Nothing 

Set currentcustomer = currentlist

If currentcustomer.CustomerName = IstCustomers.Text Then

 currentlist.Remove m_list

 UpdateList

 Exit Sub

 End If

Set currentlist = currentlist.Nextltem 

Loop

UpdateList

 End Sub

Какие же выводы можно сделать из этого примера?

Если не заметили — запустите его, переключитесь в окно отладки (Immediate window) и посмотрите, что происходит при удалении объектов из списка. Ну как, поняли?

Если бы вы действительно захотели реализовать этот путь на практике, вам пришлось бы каким-то образом разорвать циклические ссылки. В самом распространенном решении вложенный объект инициирует событие, параметром которого является переменная Object, передаваемая по ссылке. Получив это событие, контейнер должен присвоить параметру значение Me. Таким образом, объект LinkList получает ссылку на свой контейнер, после чего он может воспользоваться полученной ссылкой для сравнения или вернуть ее как значение свойства. Ссылку на контейнер необходимо немедленно освободить, чтобы избежать зацикливания. Основными недостатками подобного решения является его громоздкость и плохое быстродействие. Раннее связывание (early binding) на события не распространяется, и это может серьезно замедлить работу программы.

 

Связанные списки с применением включения BVB.NET

Начнем с прямой адаптации предыдущего примера из VB6 в VB .NET (проект LinkListNet2). Заодно вы увидите, как сильно изменился синтаксис в VB .NET. Несмотря на все различия, понять код не так уж трудно, но это лишний раз доказывает, что адаптация крупных приложений из VB6 в VB .NET — задача весьма серьезная.

ПРИМЕЧАНИЕ

 Во всех примерах программ, приведенных в этой книге, устанавливается флажок Option Strict (вкладка Build диалогового окна Project Properties). Я настоятельно рекомендую устанавливать этот флажок во всех проектах VB .NET. Обоснования будут приведены в главе 8.

Сразу бросается в глаза первое изменение: в VB .NET концепция интерфейса отделена от концепции класса (реализации интерфейса). В VB6 добавление новых методов в объект автоматически определяло его интерфейсе. Если вы захотели определить интерфейс для реализации или совместного использования в VB .NET, вам придется явно определить его как интерфейс. Ниже приведен интерфейс ILinkList1, определяемый в файле LinkList.vb.

' LinkList .Net - пример с агрегированием

' Copyright © 2001 by Desaware Inc. All Rights Reserved

' Классы теперь не реализуются - только интерфейсы. 

' поэтому связанный список определяется в виде интерфейса

 Public Interface ILinkList

 WriteOnly Property ContainerO As Object

Property NextltemO As ILinkList

Readonly Property PreviousItenKByVal Root As ILinkL,ist) As ILinkList

Sub Remove(ByRef Root As ILinkList)

Sub Append(ByRef Root As ILinkList) 

End Interface ;

1 По общепринятым правилам имена интерфейсов всегда начинаются с буквы I.

Итак, интерфейс у нас есть, но к нему еще нужна реализация (которая может использоваться объектом Customer). Объект LinkList реализует интерфейс ILinkList. На этот раз переменная m_Next указывает на интерфейс ILinkList, а не на объект LinkList. Почему? Потому что мы собираемся хранить в этой переменной ссылки на объект-контейнер, как в предыдущем примере для VB6. Контейнер будет реализовывать интерфейс ILinkList, а не объект LinkList:

Public t 

Implements ILinkList

' Данная версия демонстрирует внедрение объектов, 

'поэтому ссылка указывает на объект-контейнер

Private tn_Next As ILinkList

' Ссылка на контейнер необходима для включения

' нового элемента в список

Private m_Container As Object

Синтаксис реализации функций интерфейса в VB .NET выглядит несколько иначе. Вместо имен методов вида ILink List_Containeг в объявлении метода явно указывается, какой метод он реализует. Имя метода, используемое в классе, может не иметь ничего общего с именем метода в интерфейсе. Более того, один метод может реализовывать несколько методов интерфейса. Вместо отдельных методов Get/Set/Let блоки Get и Set теперь входят в определение свойства.

Блок Get возвращает значение свойства присваиванием имени свойства (как и в VB6 или при использовании команды Return). Блок Set получает присваиваемое значение в виде встроенной переменной Value. Поскольку в VB .NET команда Set не может использоваться для присваивания объектов, задать значение свойства можно только одним способом. Пусть имя блока Set вас не смущает: разработчики взяли методы VB6 Set и Let, объединили их и выделили в блок Set.

Синтаксис свойств тоже изменился.

' Просто вернуть ссылку на следующий объект в списке.

 ' Обратите внимание на изменения в синтаксисе. 

' См. описание команды Implements в тексте.

Public Property NextltemO As ILinkList Implements ILinkList .Nextltem

 Get

Nextltem = m_Next

 End Get 

Set(ByVal Value As ILinkList)

m_Next = Value

 End Set 

End Property

В VB6 доступность свойства только для чтения или записи определялась только наличием или отсутствием методов Get/Set. В VB .NET доступ к свойству контролируется при помощи атрибутов WriteOnly и Readonly. При ограничении доступа в реализацию свойства включается только нужный блок1.

' Контейнер задается в процессе инициализации

' Если вы захотите оформить класс в виде компонента,

' потребуется объявление с атрибутом Public

Friend WriteOnly Property Container() As Object Implements

ILinkList.Container

Set(ByVal Value As Object)

 m_Container = Value

End Set 

End Property

1 Зачем задавать атрибуты ReadOnlynWriteOnly, если язык сам сможет определить уровень доступа по наличию или отсутствию методов Set/Get? Хороший вопрос. Вероятно, разработчики могли пойти по этому пути, но использование ReadOnly/Wri teOnly лучше соответствует архитектуре .NET. Ключевые слова Readonly и WriteOnly в действительности являются атрибутами; эта тема подробно рассматривается в главе 11.

Если не считать уже упоминавшихся изменений в синтаксисе, код свойства Previousltem и метода Rеmovе очень похож на код VB6.

' В односвязном списке метод свойства Previous 

' должен провести поиск от начала списка

Public Readonly Property PreviousItem(ByVal Root As ILinkList)

 AsILinkList 

Implements ILinkList.Previousltem 

Get

Dim currentitem As ILinkList

' Помните: все ссылки относятся к объекту-контейнеру!

If (Root Is m_Container) Or (Root Is Nothing) Then 

Exit Property

End If

currentitem = Root 

Do

If currentitem.Nextltem Is m_Container Then 

Previousltem = currentitem 

Exit Property 

Else

currentitem = currentitem.Nextltem 

End If

Loop While Not currentitem Is Nothing

 End Get 

End Property

' Метод Remove находит предыдущий элемент списка

' последовательным перебором.

' Переменная Root должна передаваться по ссылке: это позволяет 

' присвоить ей новое значение, если удаляемый объект 

' находится в начале списка.

Public Sub Remove(ByRef Root As ILinkList) Implements ILinkList.Remove

 Dim previtem As ILinkList

previtem = PreviousItem(Root) 

If previtem Is Nothing Then

Root = m_Next

 Else

previtem.Nextltem = m_Next 

End If 

End Sub

Метод Append очень похож на одноименный метод VB6 и отличается от него всего в двух местах: при присваивании m_Container переменной Root и свойству Nextltem. В VB6 задача решалась простым присваиванием. В новой версии нам приходится явно преобразовывать объект      m_Cоntainеr в тип LinkList. Почему?

На первый взгляд может показаться, что перед нами пример более строгой проверки типов, воплощенной в VB .NET. До определенной степени это действительно так, но на самом деле здесь происходит нечто большее. Несколькими абзацами ниже я помогу вам найти ответ на этот вопрос, который, как я надеюсь, продемонстрирует некоторые фундаментальные различия между VB6 и VB .NET.

' Метод Append последовательно перебирает элементы 

' от Root до конца списка.

Public Sub Append(ByRef Root As ILinkList) Implements ILinkList.Append

 Dim currentitem As ILinkList

 currentitem = Root 

If Root Is Nothing Then

Root = CType(m_Container, ILinkList)

Else 

While Not currentitem.Nextltem Is Nothing

currentitem = currentitem.Nextltem

 End While

currentitem.Nextltem = CType(m_Container, ILinkList)

 End If 

End Sub

End

Объект Customer определяется в файле Customer.vb (листинг 5.3). Он реализует определенный ранее интерфейс ILinklist и обладает свойством CustomerName, как и одноименный объект из примера для VB6. Если не считать синтаксических изменений VB .NET, этот класс практически идентичен объекту VB6 Customer.

Листинг 5.3. Класс Customer из файла Customer.vb

' LinkList .Net - пример с агрегированием

' Copyright ® 2001 by Desaware Inc. All Rights Reserved

Public

' Эта версия реализует интерфейс ILinkList,

' методы которого вызываются через объекты LinkList

Implements ILinklist

Public CustomerName As String

' Функциональность обеспечивается внутренним объектом LinkList

Private m_MyLinkL1st As LinkList

Public Sub New()

MyBase.New()

m_MyLinkL1st = New LinkList()

m_MyL1'TikList .Container = Me 

End Sub

' В VB .NET объекты деинициализируются.

 ' Подробности приведены в тексте. 

Protected Overrides Sub Finalize()

 System.diagnostics.Debug.WMteLine ("Terminating customer " + _

CustomerName)

 End Sub

' Методы и свойства просто отображаются на соответствующие

1 методы и свойства LinkList

Public Sub Append(ByRef Root As ILinkList) Implements ILinkList.Append

m_MyLinkList.Append (Root) 

End Sub

Public Property NextltemO As ILinkList Implements ILinkList.Nextltem

  Set(ByVal Value As ILinkList)

m_MyLinkList.Nextltem = Value 

End Set 

Get

Nextltem = m_MyLinkList.Nextltem

End Get 

End Property

Friend WriteOnly Property ContainerQ As Object

 Implements ILinkList .Container

Set(ByVal Value As Object)

 m_MyLinkList.Container = Value

End Set

 End Property

Public Readonly Property PreviousItem(ByVal Root As ILinkList) _ 

As ILinkList Implements ILinkList.previousitern 

Get

Previousltem = m_MyLinkList.PreviousItem(Root)

End Get

End Property

Sub Remove(ByRef Root As ILinkList) Implements ILinkList.Remove

m_MyLinkList.Remove (Root)

 End Sub

End

Перейдем к определению формы, находящемуся в файле TestForm.vb. Я не буду приводить вспомогательный код, сгенерированный средой программирования, поскольку он не относится к теме. Как и в примере для VB6, в программе объявляется переменная, указывающая на первый объект в списке: 

Public bsp;

Private m_List As ILinkList

Функция UpdateList заполняет поле списка текущим перечнем клиентов. Между ее двумя версиями существует ряд важных различий.

Одно изменение несущественно: списки работают не так, как раньше. Вместо вызова метода Addltem новая строка добавляется включением нового элемента в коллекцию Items. На самом деле все элементы Windows Forms1 обладают синтаксическими и функциональными различиями по сравнению со своими эквивалентами VB6.

Также стоит обратить внимание на явное приведение переменной m_List (тип ILinkList) к объекту currentcustomer (тип Customer).

Однако самое принципиальное изменение заключается в том, что эта функция уже не использует для работы с объектом две разные переменные — для интерфейсов Customer и LinkList. Как показано в листинге 5.4, объект currentcustomer позволяет напрямую обращаться к свойствам CustomerName и Nextltem2.

Листинг 5.4. Функция обновления в TestForm.vb

' Обновление списка

' Обратите внимание на то, что необходимость в использовании разных 

переменных отпала — интерфейс ILinkList интегрируется с объектом. 

' Также обратите внимание на изменившийся синтаксис ListBox.

 Private Sub UpdateList()

IstCustomers.Items.Clear()

Dim currentcustomer As Customer

' Тем не менее повышение ссылки на интерфейс до уровня объекта

' должно сопровождаться явным преобразованием типа.

1 При неверном типе объекта произойдет ошибка стадии выполнения.

currentcustomer = CType(m_List, Customer)

Do While Not currentcustomer Is Nothing 

IstCustomers.I terns.Add (currentcustomer.CustomerName) 

currentcustomer = CType(currentcustomer.Nextltem, Customer)

Loop

 End Sub

1 Этот термин .NET обозначает все элементы управления в архитектуре .NET. Считайте их .NET-aiia-логами встроенных элементов Visual Basic или элементов ActiveX.

2 При желании можно при помощи атрибута Private скрыть методы, реализующие интерфейс, и разрешить доступ к методам интерфейса только через интерфейсные переменные;

Давайте еще раз вернемся к этим важным фактам.

Речь идет вовсе не о малозначительных деталях. В них отражены фундаментальные изменения базовой архитектуры, которые очень важно правильно понять.

Механизм работы Visual Basic 6 продиктован технологией СОМ. В СОМ для ссылок на объекты используются интерфейсные указатели, и через каждый интерфейсный указатель вызываются методы соответствующей группы. Чтобы вызвать метод другого интерфейса, вам придется предварительно получить указатель на него. Интерфейсы соответствуют типам Visual Basic, поэтому перед вызовом методов переменной необходимо присвоить объект соответствующего типа.

VB .NET не базируется на СОМ, поэтому старые правила здесь не действуют.

При реализации интерфейса в VB .NET фактически происходит наследование. Интерфейс становится составной частью объекта, его подмножеством. Присваивание ссылки на объект переменной с типом унаследованного интерфейса может выполняться напрямую. Это объясняется тем, что VB .NET уже на стадии компиляции знает, что интерфейс реализуется объектом. Например, следующий фрагмент является правильным:

Dim i I as ILinkList

 Dim eo as Customer

 со = New Customer

 il = со

Такое присваивание работает, поскольку VB .NET знает, что объект Customer всегда реализует интерфейс ILinkList.

Однако обратное неверно. Если вы попытаетесь присвоить объекту Customer ссылку на объект ILinkList, на стадии компиляции VB .NET никак не сможет определить, соответствует ли присваиваемый объект ILinkList объекту Customer. Переменная ILinkList может указывать на объект LinkList или на какой-то произвольный объект, реализующий интерфейс ILinkList. На стадии выполнения CLR может определить, на какой тип объекта ссылается данная переменная, поэтому присваивание выполняется во время выполнения программы; но поскольку его успешное выполнение не гарантировано, при попытке прямого присваивания (как в следующем фрагменте) компилятор выдает сообщение об ошибке.

Dim il as ILinkList 

Dim со as Customer

 со = New Customer

 со = il

Вместо этого необходимо выполнить явное преобразование типа функцией СТуре:

со = CType(il, Customer)

Тем самым вы сообщаете компилятору, что он должен довериться вам и выполнить преобразование так, как приказано. Если il не содержит ссылки на объект Customer, CLR инициирует ошибку времени выполнения (CLR не допускает присваивание объектам при несовпадении типов). Возникает резонный вопрос: если CLR все равно проверяет тип во время выполнения, зачем нужно явное преобразование? Ведь у VB6 хватает сообразительности для того, чтобы выполнить эти преобразования за вас. В действительности мы имеем дело с такой замечательной новой возможностью VB .NET, как жесткая проверка типов (Strict Type Checking). Если сбросить этот флажок, VB .NET выполнит преобразования автоматически и вам не придется заниматься явным преобразованием. Тем не менее на страницах этой книги я неоднократно рекомендую не делать этого. Жесткая проверка типов — одна из самых замечательных особенностей VB .NET. Она улучшает программу, сокращает количество ошибок и ускоряет их диагностику. Начиная работу над любым проектом VB .NET, непременно включите жесткую проверку типов1.

1Потому что Microsoft зачем-то оставила этот флажок сброшенным по умолчанию.

Поскольку объект Customer реализует интерфейс ILinkList, все методы этого интерфейса напрямую доступны для объекта Customer. Старые правила СОМ здесь не действуют. Поскольку объект Customer является надмножеством методов, свойств и всех реализованных интерфейсов объекта Object, он может поддерживать все его методы, а также методы всех реализованных интерфейсов.

В остальных функциях формы вы разберетесь без труда. Они очень похожи на функции примера VB6, если не считать синтаксических изменений, о которых говорилось выше. Также на форме появилась новая кнопка cmdGC, при нажатии которой выполняется сборка мусора. Попробуйте щелкнуть на этой кнопке после исключения объекта из списка — вы увидите, что объект успешно удаляется. Это доказывает, что в VB .NET нет проблемы с циклическими ссылками.

Protected Sub cmdRemove_CHck(ByVal sender As System.Object,

ByVal e As System.EventArgs) Handles cmdRemove.Click 

Dim currentcustomer As Customer

currentcustomer = CType(m_L1st, Customer)

Do While Not currentcustomer Is Nothing 

If currentcustomer.CustomerName = 

CStr(lstCustomersO.Selectedltem) Then

currentcustomer.Remove (m_L1st)

UpdateUst()

Exit Sub 

End If

currentcustomer = CType(currentcustomer.Nextltem, Customer)

 Loop 

UpdateL1st()

End Sub

Protected Sub cmdAdd_Click(ByVal sender As System.Object_

 ByVal e As System".EventArgs) Handles cmdAdd.Click

Dim newEntry As New Customer()

newEntry .CustomerName = txtCustomerNameO .Text

newEntry.Append (m_List)

UpdateL1st()

End Sub

' При нажатии кнопки GC выполняется немедленная сборка мусора.

' Убедитесь в том, что исключенные объекты успешно уничтожаются, 

' несмотря на наличие циклических ссылок.

* Protected Sub cmdGC_Click(ByVal sender As System.Object, _ 

ByVal e As System.EventArgs) Handles cradGC.Click

gc.Collect()

gc.Wai tForPendingFinalizers() 

End Sub

End

Подведем итог.

  •  В VB .NET решена проблема с циклическими ссылками, присутствующая в решении для VB6.
  •  Обеспечивая прямой доступ ко всем методам и свойствам объекта и реализованных им интерфейсов, VB .NET радикально упрощает программирование при использовании методики включения.
  •  Объем программного кода, реализующего включение в объекте Customer, остается минимальным.

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

 

Связанные списки с применением наследования в VB .NET

В определенном смысле вы уже встречались с наследованием. Команда Implements обеспечивает то, что называется «наследованием интерфейса» — такая возможность существует и в VB6. Под «наследованием интерфейса» понимается реализация объектом всех методов интерфейса, определенных где-то в другом месте (в определении интерфейса в VB .NET или в определении класса в VB6). При наследовании интерфейса объект доступен как с помощью переменной, имеющей тип интерфейса, так и переменной того же типа, что и сам объект.

Проект LinkListNetlnh демонстрирует то наследование, о котором так много говорили, — полноценное наследование реализации1. При наследовании реализации объект наследует от базового класса не «правила поведения», а фактическую реализацию и при желании может переопределить ее. При этом объект наследует от базового класса реальную функциональность, которую при желании можно переопределить. В таком сценарии объект доступен с помощью переменной, имеющей тип либо базового, либо производного класса.

В листинге 5.5 изменился только объект Customer (файл Customer.vb).

1 Возможно, вы также слышали о так называемом «визуальном наследовании» — этот термин обычно встречается в маркетинговой информации о .NET. Ничего подобного в природе не существует. То, что «пиарщики» называют визуальным наследованием, в действительности представляет собой обычное наследование, примененное к объекту (например, форме или элементу) с визуальными характеристиками (выводимому на экран или на принтер, обладающему пользовательским интерфейсом и т. д.).

Листинг 5.5. Объект Customer в проекте LinkListNetlnh

' LinkList .Net - пример с агрегированием 

' Copyright ® 2001 by Desaware Inc. All Rights Reserved

Public  

nherits LinkList

Public CustomerName As String

Public Sub New()

MyBase.New()

Me.Container = Me 

End Sub

Protected Overrides Sub Finalize()

 System.diagnostics.Debug.WriteLine ("Terminating customer " +_

CustomerName)

 End Sub

' Создавать внутренний объект "LinkList" не нужно -

' объект Customer также является и объектом LinkList. 

' Реализация интерфейса ILinkList тоже не нужна, 

' его методы и свойства уже реализованы базовым классом.

End

Закрытый объект LinkList куда-то исчез. Вместе с ним исчезли и все реализованные функции. Когда объект Customer наследует от объекта LinkList, он «становится» объектом LinkList. Более того, мы могли бы пойти еще дальше и модифицировать объект LinkList так, чтобы в нем не использовалась переменная Container — объекты LinKList и Customer стали одним и тем же.

Здорово, не правда ли?

Вообще-то не очень.

Да, эта программа работает. Она экономит несколько строк кода и берет на себя все операции с внутренним объектом. Однако такое решение совершенно не изменяет клиентскую сторону, а работать с объектом Customer ничуть не проще.

Но самый страшный недостаток этого кода относится не столько к технической, сколько к архитектурной стороне дела. Его можно сформулировать следующим образом.

  •  Объект Customer может моделировать многие сущности, например отдельную личность, компанию и т. д. Но он ни при каких условиях не моделирует связанный список.

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

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

Как ни странно, подобные связи возникают в приложениях не так уж часто. С другой стороны, они постоянно встречаются при построении библиотек классов, используемых разработчиками. В частности, как вы вскоре убедитесь, наследование широко применяется в архитектуре .NET. Да, в каждом написанном вами

приложении или компоненте будут создаваться объекты, производные от классов .NET. Но вам практически не придется создавать объекты, от которых будут порождаться другие объекты.

Впрочем, решение проблемы циклических ссылок в сочетании с простотой вызова методов разных унаследованных интерфейсов позволяет легко и эффективно организовать повторное использование программного кода.

 

Двойной связанный список

Помимо архитектурных соображений, против применения наследования в данном примере имеются и сугубо практические доводы. Связанные списки часто используются для размещения объектов в определенном порядке. А вдруг потребуется отсортировать объекты по двум критериям сразу, чтобы объект присутствовал сразу в двух списках? Наследовать один и тот же интерфейс дважды нельзя. Можно определить новый интерфейс для поддержки двух связанных списков, но обычно в таких ситуациях используется другой подход, продемонстрированный в проекте LinkListNetDual.

В листинге 5.6 приведена новая версия класса LinkList. Она похожа на предыдущую, однако ссылка на контейнер оформлена в виде открытого свойства. Кроме того, использованная архитектура также отличается от предыдущего примера и следует схеме, показанной на рис. 5.1: ссылка теперь указывает на объект LinkList, а не на объект-контейнер. Для получения ссылки на контейнер используется свойство Container.

Листинг 5.6. Новая версия LinkList: двойное связывание в одном объекте

' LinkList .Net - пример с двойным связыванием

' Copyright ©2001 by Desaware Inc. All Rights Reserved

Public Interface ILinkList

' В этой версии связи устанавливаются между внутренними

' объектами LinkList.

Property NextltemO As ilinklist

1 В этой версии все элементы списка относятся к типу LinkList,

 ' поэтому для обращения к контейнеру используется 

' открытое свойство Container.

 Property Container() As Object

Readonly Property PreviousItem(ByVal Root As ILinkList) As ILinkList

 Sub Remove(ByRef Root As ItinkList)

Sub Append(ByRef Root As ILinkList) 

End Interface

Public t 

Implements ILinkList

' В этой версии связи устанавливаются 

' между внутренними объектами LinkList.

 Private m_Next As ILinkList

' Получение ссылки на контейнер

 Private m_Container As Object

Friend Property Container() As Object Implements ILinkList.Container

 Get

Container = m_Conta1ner 

End Get 

Set(ByVal Value As Object)

m_Container = Value 

End Set 

End Property

' Просто вернуть ссылку на следующий объект в списке.

Public Property NextltemO As ILinkList Implements ILInklist.Nextltem

Get

Nextltem = m_Next

End Get

Set(ByVal Value As ILinkList) 

m_Next = Value

End Set

 End Property

' В односвяэном списке метод свойства Previous

 ' должен провести поиск от начала списка.

Public Readonly Property PreviousItem(ByVal Root As ILinkList) 

As IL1nkL1st Implements ILinkLlst.PreviousItem 

' Get

Dim currentitem As ILinkList 

If (Root Is Me) Or (Root Is Nothing) Then

Exit Property 

End If

currentitem = Root 

Do

If currentitem.Nextltem Is Me Then 

Prevlousltem = currentitem 

Exit Property 

Else

currentitem = currentitem.Nextltem

 End If

Loop While Not currentitem Is Nothing

 End Get 

End Property

' Метод Remove находит предыдущий элемент списка 

' последовательным перебором.

' Переменная Root должна передаваться по ссылке: это позволяет 

' присвоить ей новое значение, если удаляемый объект 

' находится в начале списка.

Public Sub Remove(ByRef Root As ILinkList) Implements ILinkList.Remove

 Dim prevltem As ILinkList

previtem = PrevlousItem(Root)

 If previtem Is Nothing Then

Root = m_Next ' Else

previtem.Nextltem = m_Next

 End If 

End Sub

1 Метод Append последовательно перебирает элементы

 ' от Root до конца списка.

Public Sub Append(ByRef Root As ILinkList) Implements ILinkList.Append

 Dim currentitem As ILinkList 

currentitem = Root

 If Root Is Nothing Then

Root = Me

 Else 

While Not currentitern.Nextltem Is Nothing

currentitem = currentitern.Nextltem 

End While

currentitern.Nextltem = Me 

End If 

End Sub

End

Объект Customer (листинг 5.7) переработан весьма основательно, поскольку он должен поддерживать принадлежность объекта к двум спискам одновременно. Объект уже не наследует интерфейс ILinkList: при таком подходе он ограничивался бы всего одним связанным списком. Вместо этого объект поддерживает разные методы для разных списков (например, Nextlteml и Nextltem2).

Такой подход увеличивает объем программного кода. Это объясняется тем, что объект LinkList умеет объединять в список только объекты, реализующие интерфейс ILinkList (как и он сам).

Листинг 5.7. Объект Customer с поддержкой нескольких связанных списков

' LinkList .Net - пример с двойным связыванием 

' Copyright ©2001 by Desaware Inc. All Rights Reserved 

Public  

Public CustomerName As String

' Эта версия показывает, как включить один элемент сразу

' в два списка. Обратите внимание: объект НЕ реализует

' LinkList командой Implement. Хотя внутренние связи

' устанавливаются между объектами LinkList, окружающий мир

' видит только объекты Customer; таким образом,

' все параметры и возвращаемые значения методов

1 относятся к типу Customer, а не LinkList.

Private m_MyLinkListl As ILinkList

Private m_MyLinkList2 As ILinkList

Public Sub New()

MyBase.New()

m_MyLinkListl = New LinkList()

m_MyLinkListl.Container = Me

m_MyLinkList2 = New LinkList()

m_MyLinkList2.Container = Me

 End Sub

Protected Overrides Sub Finalize() 

System.diagnostics.Debug.WriteLine ("Terminating customer " + _

CustomerName) 

End Sub

' Поскольку ссылка указывает на внутренний объект,

 ' необходимо организовать доступ к нему из других 

' элементов списка.

Friend Readonly Property LinkListl() As ILinkList

Get

LinkListl = m_MyLinkl_istl

End Get 

End Property

Friend Readonly Property LinkList2() As ILinkList

Get LinkList2 = m_MyLinkList2

End Get 

End Property

' Функции требуют некоторой дополнительной работы для проверки 

' граничных условий (например, пустого списка).

 Public Sub Appendl(ByRef Root As Customer)

 If Root Is Nothing Then

Root = He

 Else

' Если Root = Nothing, произойдет ошибка.

 m_MyLinkLi stl.Append (Root.m_MyLinkListl) 

End If

 End Sub

Public Sub Append2(ByRef Root As Customer) 

If Root Is Nothing Then

Root = Me

 Else

m_MyLinkList2.Append (Root.m_MyLinkList2)

 End If 

End Sub

' Еще раз посмотрите, как реализация использует интерфейс

 ' ILinkList, а пользователи объекта Customer видят только 

' ссылки на объекты Customer.

Public Readonly Property NextltemH) As Customer

 Get

Dim nextref As ILinkList nextref = m_MyLinkListl.NextItem 

' Необходима явная проверка nextref, 

' в противном случае вызов enextref.Container

 ' завершится неудачей. 

If nextref Is Nothing Then

nextiteml = Nothing 

Else

' nextref.container относится к типу Object.

' Необходимо явное преобразование типа, 

nextiteml = CType(nextref.Container, Customer)

 End If

End Get 

End Property

Public Readonly Property Nextltem2()

 As Customer Get

Dim nextref As ILinkList nextref = m_MyLinkList2.NextItem ; 

If nextref Is Nothing Then

nextitem2 = Nothing 

Else

nextitem2 = CType(nextref.Container, Customer)

 End If

 End Get 

End Property

Public Readonly Property PreviousIteml(ByVal Root As Customer) 

As Customer Get

Previouslteml = CType(m_MyLinkListl.PreviousItem(Root.LinkListl), _

Customer)

 End Get 

End Property

Public Readonly Property Previousltem2(ByVal Root As Customer) 

As Customer Get

Previousltem2 = CType(m_Myl_inkList2 . PreviousItem(Root. LinkLi stl) , _

Customer) 

End Get

 End Property

Sub RemoveKByRef Root As Customer)

Dim llroot As ILinkList

llroot = Root.LinkListl

' Почему бы не воспользоваться командой

' m_MyLinkLi st. Remove Root. LinkLi stl?

' Потому что Root.LinkListl копируется

' во временную переменную, которая затем

' передается по ссылке; изменения этой временной

' переменной никак не отразятся в Root.LinkListl.

' Следовательно, для проверки изменений необходимо

' использовать промежуточную переменную.

m_MyLinkListl.Remove (llroot)

If llroot Is Nothing Then Root = Nothing

Else Root = CType(llroot.Container, Customer)

End If 

End Sub

Sub Remove2(ByRef Root As Customer)

Dim llroot As ILinkList

llroot = Root.LinkList2

m_MyLinkList2.Remove (llroot)

If llroot Is Nothing Then Root = Nothing

Else Root = CType(llroot.Container, Customer)

End If 

End Sub

End

В листинге 5.8 приведен обновленный код формы TestForm.vb. На форме появился второй список для клиентов, имена которых начинаются с букв А-М. Обратите внимание: обе переменные со ссылками на списки (m_Li st и m_ListAToM) теперь относятся к типу Customer. В сущности, форма и не подозревает о существовании интерфейса ILinkList, который теперь все равно не может использоваться для обращения к объекту Customer, поскольку он не участвует в наследовании. В остальном код формы напоминает уже виденное, если не считать дополнений для работы с двумя списками.

Листинг 5.8. Форма TestForm.vb с поддержкой двух связанных списков

' LinkList .Net - пример с двойным связыванием

' Copyright ©2001 by Desaware Inc. All Rights Reserved

Public bsp;

Inherits System.Windows.Forms.Form

' Обратите внимание: корневые ссылки теперь относятся к типу 

' Customer вместо типа LinkList.

 Private m_List As Customer 

Private m_ListAtoM As Customer

Protected Sub cmdGC_Click(ByVal sender As System.Object, ByVal e As

  System.EventArgs) Handles cmdGC.Click

gc.CoUect()

gc.WaitForPendingFinalizers() 

End Sub

' Удаление из обоих списков

Protected Sub cmdRemove_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdRemove.Click 

Dim currentcustomer As Customer

currentcustomer = m_List

 Do While Not currentcustomer Is Nothing

If currentcustomer.CustomerName = CStr(IstCustomersO.Selectedltem) Then currentcustomer.Remove1 (m_Li st)

 Exit Do

 End If

currentcustomer = currentcustomer.Nextlteml 

Loop

currentcustomer = m_ListAtoM

 Do While Not currentcustomer Is Nothing 

If currentcustomer.CustomerName = _

 CStr (IstCustomersO .Selectedltem) Then 

currentcustomer.Remove2 (m_ListAtoM)

 Exit Do 

End If

currentcustomer = currentcustomer.Nextltem2

 Loop

UpdateListO 

End Sub

' В этом простом примере все клиенты с именами >"М

' исключаются из второго списка.

' Также обратите внимание на то, что нам уже не приходится

1 использовать разные переменные Customer и LinkList -

' мы всегда работаем только с Customer.

Protected Sub cmdAdd_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles cmdAdd.Click

 Dim newEntry As New Customer()

 newEntry .CustomerName = txtCustomerName()

  .Text newEntry.Appendl (m_List) If UCase(Strings.Left(newEntry.CustomerName, 1)) <= "M" Then

newEntry.Append2 (m_ListAtoM)

 End If

UpdateList()

 End Sub

' Вывести содержимое обоих связанных списков 

Private Sub UpdateList()

IstCustomersO.Items.Clear()

IstAtoMO .Items.Clear()

Dim currentcustomer As Customer

currentcustomer = m_List

Do While Not currentcustomer Is Nothing

IstCustomers()

 . I terns. Add (cur rent customer. Customer Name)

currentcustomer = currentcustomer.Nextlteml 

Loop

currentcustomer = m_ListAtoM

Do While Not currentcustomer Is Nothing

IstAtoM() . I terns. Add (currentcustomer .CustomerName)

currentcustomer = currentcustomer.Nextltem2

 Loop

End Sub

End

Проект LinkListVB6-2 показывает, как реализовать двойное связывание в VB6 (однако и в этом примере существует проблема циклических ссылок).

 

Конфликты имен

Итак, VB .NET предоставляет все методы и свойства наследуемых объектов и интерфейсов пользователям объекта верхнего уровня. А что произойдет, если в двух интерфейсах будут определены методы с одинаковыми именами?

На самом деле ответ уже встречался в примерах программ, хотя этот вопрос и не ставился напрямую. Секрет кроется в секции Implements объявления метода. В листинге 5.9 приведен класс ont> из проекта Twolnterfaces. В пространстве имен определены два интерфейса, содержащие методы с одинаковыми именами CommonFunction.

Листинг 5.9. Реализация интерфейсов с одинаковыми именами методов

' Пример разрешения конфликтов имен в интерфейсах

' Copyright ©2001 by Desaware Inc. All Rights Reserved

Interface MyFirstlnterface

Sub UniqueFunction()

Sub CommonFunction()

 End Interface

Interface MySecondlnterface

Sub SecondUniqueFunction()

Sub CommonFunction()

End Interface 

Public mplements MyFirstlnterface 

' Implements MySecondlnterface

Sub UniqueFunction()

 Implements MyFirstlnterface.UniqueFunction 

End Sub

Sub SecondUniqueFunctionO Implements _ MySecondlnterface.SecondUniqueFunction

End Sub

' Эти функции следовало бы объявить закрытыми,

' чтобы избежать возможных недоразумений.

Sub CommonFunctionO Implements MyFirstlnterface.CommonFunction

Console.WriteLine ("Common Function on first interface")

 End Sub

Sub CommonFunctionSecondlnterface() _

Implements MySecondlnterface.CommonFunction 

Console.WriteLine ("Common function on second interface")

End Sub

End

Как видите, проблема решается переименованием одного из методов. Синтаксис команды Implements позволяет назначать реализованным функциям разные имена внутри класса, благодаря чему вы можете (а вернее, обязаны) избавиться от конфликтов имен.

' Пример разрешения конфликтов имен в интерфейсах

' Copyright ©2001 by Desaware Inc. All Rights Reserved

Module Modulel

Public Sub Main()

Dim с As New >

Dim il As MySecondlnterface

c.CommonFunctionO

il = с

il. CommonFunction()

Console.ReadLine()

 End Sub

End Module

Проблемы с реализацией решены, но как насчет вызывающей стороны?

При вызове открытого метода CommonFunction (команда с .CommonFunction) выводится строка Common Function on first interface. Однако при вызове CommonFunction через переменную il (iI.CommonFunction) будет выведена строка Common Function on second inter face; следовательно, при этом вызывается метод CommonFunctionSecondlnterfасе.

Подобных ситуаций следует избегать из-за высокой вероятности ошибок. Простейшее решение заключается в ограничении доступа к методу на уровне класса, для чего метод объявляется с атрибутом Private. Метод всегда можно вызвать через интерфейсную переменную, поскольку в интерфейсе метод объявлен открытым.

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

 

Наследование в .NET

Наследование широко используется в архитектуре .NET. Среда Common Language Runtime поддерживает только одиночное наследование. Если бы эта книга предназначалась для программистов C++, в этот момент я бы начал подробно расписывать преимущества одиночного наследования перед множественным. Поскольку книга написана для программистов Visual Basic, вряд ли стоит тратить время на разговоры о множественном наследовании. Впрочем, если вам кажется, будто вас обделили, я скажу следующее: как бы скептически я ни относился к наследованию, к множественному наследованию я отношусь в десять раз хуже. Оно создает в программе множество сложностей и дает слишком мало преимуществ. Я помню всего один случай эффективного применения множественного наследования — ATL. Кстати, это одна из причин, по которой ATL так трудно изучать. Я использовал множественное наследование в своей работе всего один раз, да и то по ошибке1.

1 На всякий случай сообщаю, что в С# множественное наследование тоже не поддерживается.

 

Объекты, сплошные объекты...

Любые разговоры о наследовании в .NET должны начинаться с объектов2. Как упоминалось выше, для борьбы с утечкой памяти CLR следит за объектами и освобождает их в тот момент, когда на них отсутствуют явные ссылки. Но что происходит с данными, которые не являются объектами? Как CLR управляет этими данными и обеспечивает их своевременное освобождение?

Весьма каверзный вопрос.

2 Постойте-ка! Разве мы не говорили о наследовании с самого начала главы? Да, но тогда речь шла о наследовании как концепции и возможности языка. Сейчас мы переходим к использованию наследования в .NET.

В CLR любой элемент данных является объектом. Каждая структура — это объект. Даже обычное целое число — тоже объект. Просмотрите модуль Modulel.vb из проекта IntegerObject, приведенный в листинге 5.10.

Листинг 5.10. Консольное приложение IntegerObject

' Демонстрация объектной сущности всех данных.

' Copyright ©2001 by Desaware Inc. All Rights Reserved

Module Modulel

Sub Main()

console.WriteLine ("This is a test")

Dim i As Integer =15

console.Writeline (i .ToString())

console.WriteLine. ("Hash is: " + i .GetHashCodeO .ToString())

console.WriteLine ("Type is: " + i .GetTypeO .ToString)

console.WriteLine ("Type full name is: " + i.GetTypeO.FullName())

console.WriteLine ("Type assembly qualified name is: " + _

i.GetTypeO.AssemblyQualifiedName) console.WriteLine ("Type assembly qualified name is: " + _

i .GetType() .Namespace)

console.Write ("Press Enter to continue") console.ReadLine()

End Sub 

End Module

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

В этом фрагменте заслуживает внимания целочисленная переменная i. Хотя она объявлена с типом Integer, для нее можно вызывать методы. Вы когда-нибудь слышали о целых числах с методами?

Это объясняется тем, что тип Integer, как и любой тип переменной, создается производным от типа Object и для него, как для любого объекта, определяются некоторые методы. Метод ToString возвращает строковое представление данных переменной. Метод GetHashCode возвращает хэш-код объекта, используемый при поиске в коллекциях объектов. Метод Equals (в листинге не приведен) предназначен для сравнения объектов; он позволяет сравнивать объекты на основании внутренних данных объекта вместо простого сравнения ссылок в двух переменных. Метод GetType возвращает описание типа объекта — метаданные, содержащие полную информацию о свойствах и методах объекта.

Выходные данные программы IntegerObject выглядят так1:

This is a test

15

Hash is: 15

Type is: Int32

Type full name is: System.Int32

Type assembly qualified name is: System. Int32, mscorlib, Version=1.0..... ,Cul

ture=neutral,PublicKeyToken=b77a5c561934e089Type assembly qualified name is:

System

Press Enter to continue

1 Вероятно, в вашем случае значения Version и PublicKey будут другими. Полное имя зависит от того, используете ли вы бета-версию, промежуточную сборку или окончательную версию исполнительной среды .NET.

Как правило, для ranajnteger из всех базовых методов вызывается только метод ToString. Другие методы обычно используются в более сложных объектах, которые переопределяют встроенную реализацию и оптимизируют ее для своих целей. О переопределении функций будет рассказано ниже.

Преобразование стандартных типов данных в объекты обеспечивает логическое единство, необходимое CLR для управления данными в приложениях. Возникает резонный вопрос: а не слишком ли дорого за это приходится платить и не повлияет ли поддержка методов для простых целочисленных переменных на быстродействие приложения? Ведь целое число обычно представляет собой простую переменную, выделенную в стеке, а объект неизбежно приводит к лишним затратам ресурсов.

К счастью, CLR неплохо справляется с этой проблемой. Оказывается, в .NET объекты делятся на две категории. Ссылочные (reference) объекты вам хорошо знакомы — они реализуются при помощи классов, хранятся в куче, а для обращения к ним используются ссылки. К категории структурных (value) объектов относятся «облегченные» объекты, которые могут храниться в стеке. Пока структурные объекты используются как простые переменные (например, в математических операциях для целых чисел), компилятор генерирует точно такой же код, как и в текущей версии Visual Basic. Загрузка, сохранение и математические вычисления выполняются непосредственно с числовыми данными. Но при использовании структурного объекта в ссылочном контексте CLR выполняет операцию, называемую упаковкой (boxing) — данные преобразовываются во временный объект, с которым выполняется необходимая операция, после чего объект снова распаковывается (unboxing). Для объявления объектов структурного типа в Visual Basic используется ключевое слово Structure (см. главу 6).

 

Формы

До настоящего момента мы не обращали внимания на код формы, сгенерированный дизайнером форм. В листинге 5.11 приведен фрагмент приложения LinkListNet2. В пространстве имен System.WinForms собраны реализации различных Windows-компонентов. Новые формы наследуют от объекта System.WinForms.Form. :

Полная иерархия объекта Form выглядит следующим образом:

Object

MarshalByRefObject 

Component 

Control

ScrollableControl 

ContainerControl

Form 

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

Просматривая листинг 5.11, помните, что все вызываемые методы наследуются от одного из базовых классов. Еще раз подчеркну, что этот код приведен не для подробного анализа, а лишь для демонстрации наследования в .NET.

Листинг 5.11. Файл TestForm.vb (проект LinkListNet2)

' LlnkLlst .Net - пример с агрегированием

1 Copyright © 2001 by Desaware Inc. All Rights Reserved

Public sp;

Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

Public Sub New()

  MyBase.New()

' Необходимо для дизайнера форм Windows.

 InitializeComponent()

' Дальнейшая инициализация выполняется

' после вызова InitializeComponent(). 

End Sub

' Форма переопределяет Dispose для очистки списка компонентов.

 Public Overloads Overrides Sub Dispose()

MyBase.Dispose()

If Not (components Is Nothing) Then

 components.Dispose()

End If

 End Sub

Private WithEvents cmdAdd As System.Windows.Forms.Button 

Private WithEvents IstCustomers As System.Windows.Forms.List-Box

 Private WithEvents cmdGC As System.Windows.Forms.Button 

Private WithEvents txtCustomerName As System.Windows.Forms.TextBox

 Private WithEvents cmdRemove As System.Windows.Forms.Button 

Private WithEvents labell As System.Windows.Forms.Label

' Необходимо для дизайнера форм Windows.

Private components As System.ComponentModel.Container

' ВНИМАНИЕ: следующий фрагмент необходим для дизайнера 

' форм Windows.

' Для его модификации следует использовать дизайнер форм. 

' Не изменяйте его в редакторе!

<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

Me. txtCustomerName = New System.Windows. Forms.TextBox()

Me.cmdGC = New System.Windows.Forms.Button()

Me.labell = New System.Windows.Forms.Label()

Me.cmdAdd = New System.Windows.Forms.Button()

Me.IstCustomers = New System. Windows. Forms. ListBox()

Me. cmdRemove = New System.Windows . Forms . Button()

Me.SuspendLayout()

'txtCustomerName

Me.txtCustomerName.Location = New System.Drawing.Point(88, 24)

Me.txtCustomerName.Name = "txtCustomerName"

Me.txtCustomerName.Size = New System.Drawing.Size(136, 20)

Me.txtCustomerName.Tablndex =4

Me.txtCustomerName.Text = ""

'cmdGC

Me.cmdGC.Location = New System.Drawing.Point(200, 152)

Me.cmdGC.Name = "cmdGC"

He.cmdGC.Size = New System.Drawing.Size(64, 32)

Me.cmdGC.Tablndex = 2

Me.cmdGC.Text = "GC"

'labell

Me.labell.Location = New System.Drawing.Point(16, 24)

Me.labell.Name = "labell"

Me.labell.Size = New System.Drawing.Size(64, 16)

Me.labell.Tablndex = 5

Me.labell.Text = "Customer:"

Me.labell.TextAlign = System.Drawlng.ContentAlignment.MiddleRight

'cmdAdd

Me.cmdAdd.Location = New System.Drawing.Point(206, 72)

Me.cmdAdd.Name = "cmdAdd"

Me.cmdAdd.Size = New System.Drawing.Size(64, 32)

Me.cmdAdd.Tablndex = 9

Me.cmdAdd.Text = "Add"

'IstCustomers

Me.IstCustomers.Location = New System.Drawing.Point(24, 72) Me.IstCustomers.Name = "IstCustomers"

Me.IstCustomers.Size = New System.Drawing.Size(160, 108) Me.IstCustomers.Tablndex = 3

'cmdRemove

Me.cmdRemove.Location = New System.Drawing.Point(206, 112)

Me.cmdRemove.Name = "cmdRemove"

Me.cmdRemove.Size = New System.Drawing.Size(64, 32)

Me.cmdRemove.Tablndex = 1

Me.cmdRemove.Text = "Remove"

1Forml

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)

 Me.ClientSize = New System.Drawing.Size(292. 216)

  Me.Controls.AddRange(New System.Windows.Forms.Control() 

{Me.labell, Me.txtCustomerName, Me.IstCustomers. Me.cmdGC, _

  Me.cmdRemove, Me.cmdAdd}) 

Me.Name = "Forml"

 Me.Text = "Link List Test 2"

 Me.ResumeLayout (False)

End Sub

Наследование в VB .NET

Несомненно, вам придется часто использовать наследование, по крайней мере по отношению к объектам .NET. Пора поближе познакомиться с поддержкой наследования на уровне языка.

В приведенном ниже проекте Customers 1 приведен классический пример наследования. Некая организация работает с несколькими категориями клиентов. Все классы клиентов являются производными от общего базового класса Customer, объявленного с атрибутом Mustlnherit. Это означает, что создавать объекты типа Customer в программе нельзя1; допускается лишь создание объектов классов, производных от Customer. Классы также могут объявляться с атрибутом Notinheri table, запрещающим создание производных классов.

1 В ООП такие классы принято называть «абстрактными». — Примеч. перев.

1 Приложение Customers

1 Copyright ©2001 by Desaware inc. All Rights Reserved

 Public Mustlnherit  

Public Name As String

Public MustOverride Function DefaultNet() As Integer

 Public Overridable Function DisplayTerms() As String 

DisplayName()

Console.WriteLine ("... is net " + CStr(DefaultNet()))

 End Function 

Sub DisplayName()

Console.WriteLine ("Customer is " + Name)

 End Sub

End

Методы и свойства класса могут помечаться ключевыми словами Overridable и MustOverride.

Если метод помечен ключевым словом Overridable, производный класс при желании может переопределить его и предоставить собственную реализацию. Если это ключевое слово отсутствует, попытки производного класса создать метод с тем же именем приведут к ошибкам на стадии компиляции. Ключевое слово MustOverride означает, что производный класс обязан реализовать данный метод. Естественно, поскольку такой метод всегда реализуется в производных классах, реализовывать его в базовом классе не обязательно.

Переопределение непомеченных свойств и методов по умолчанию запрещается. Для всех методов базового класса с ключевым словом Overridable возможность переопределения сохраняется в производном классе и во всех классах, производных от него. Чтобы запретить дальнейшее переопределение метода, достаточно переопределить его в одном из производных классов с ключевым словом NotOverridable.

В главе 10 вы также научитесь использовать ключевое слово Overloads для объявления методов с именами, совпадающими с именами методов базового класса, но с другими параметрами. Пока я не буду останавливаться на этой возможности, чтобы не усложнять ситуацию.

Рассмотрим три класса, производных от Customer. Класс Commercial переопределяет функцию DefaultNet, чтобы установить стандартный размер платежей для коммерческих организаций. Все остальные методы и свойства этого класса наследуются от базового класса.

Public al

 Inherits Customer

 Public Overrides Function DefaultNet() As Integer

Return (30) 

End Function 

End

Класс Individual также переопределяет метод DisplayTerms таким образом, чтобы вместо описания условий платежа выводилось сообщение о необходимости немедленной оплаты.

Public ual

 Inherits Customer

 Public Overrides Function DefaultNet() As Integer

Return (0)

 End Function

 Public Overrides Function DisplayTerms() As String

DisplayName()

Console.WriteLine ("... must pay immediately")

 End Function

End

Класс Government, помимо переопределения метода DisplayTerms, определяет новый метод Branchlnfo, который представляет отдел правительственного учреждения. Иначе говоря, объект расширяет функциональность базового класса и добавляет в него новые возможности.

Public nt 

Inherits Customer 

Public Overrides Function DefaultNet() As Integer

Return (120)

 End Function 

Public Overrides Function DisplayTerms() As String

Di splayName()

Console.WriteLine ("... will pay someday we hope") 

End Function 

Sub Branchlnfo()

Console.WriteLine ("Legislative")

 End Sub

End

Модуль Modulel.vb (листинг 5.12) содержит простое консольное приложение, демонстрирующее применение этих классов.

Листинг 5.12. Модуль Modulel.vb из проекта Customer1

' Приложение Customers

' Copyright ©2001 by Desaware inc. All Rights Reserved

Module Modulel

Sub Main()

Dim store As New Commerical()

 store.Name = "Worst buys"

 store.DisplayName() 

store.DisplayTerms()

console.Write ("Press enter to continue:")

 console.ReadLine() 

Dim Person As New Individual()

 Person.Name = "Jim Smith"

 Person.DisplayName ()

 Person.DisplayTerms()

console.Write ("Press enter to continue:")

console. ReadLine()

Dim USA As New Government(,

USA.Name = "U.S.A."

USA.DisplayName()

USA. DisplayTerms()

USA.BranchInfo()

console.Write ("Press enter to continue:")

console.ReadLine ()

Dim BaseObject As Customer

BaseObject = USA

BaseObject.DisplayName()

BaseObject.DisplayTerms()

1 BaseObject.Branchlnfo()

console. ReadLine()

End Sub

End Module

Результат выглядит следующим образом:

Customer is Worst buys

Customer is Worst buys

... is net 30

Press enter to continue:

Customer is Jim Smith . •

Customer is Jim Smith

... must pay immediately

Press enter to continue:

Customer is U.S.A.

Customer is U.S.A.

... will pay someday we hope

Legislative

Press enter to continue:

Customer is U.S.A.

Customer is U.S.A.

... will pay someday we hope

Legislative

Press enter to continue:

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

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

Следующий интересный момент связан с функцией DisplayTerms. Мы используем ссылку на базовый класс Customer. Базовый класс имеет собственную реализацию DisplayTerms, однако при вызове DisplayTerms для объекта базового класса вызывается метод объекта Government. Как такое возможно?

CLR знает тип объекта, с которым вы работаете, даже если доступ к нему осуществляется через объект базового класса. Во время выполнения программы CLR обнаруживает, что метод был переопределен, и вызывает правильный метод. Этот механизм называется полиморфизмом и входит в число основных особенностей любого настоящего объектно-ориентированного языка. Впрочем, и полиморфный вызов методов можно изменить, используя ключевое слово Shadows вместо Overrides. Ключевое слово Shadows означает, что метод производного класса, хотя и имеет такое же имя, является совершенно другой функцией и вызовы через объект базового класса не должны передаваться новому методу.

В данном примере метод Branchlnfo невозможно вызвать для переменной BaseOb ect. Хотя производный класс может использовать методы и свойства базового класса, обратное невозможно: базовый класс не может использовать методы и свойства, добавленные в производном классе.

В главе 10 более подробно рассматриваются различные атрибуты и ключевые слова, влияющие на наследование в .NET.

 

Решение проблемы неустойчивости базового класса

А сейчас я продемонстрирую одну из самых впечатляющих возможностей CLR и ее применение при решении одной проблемы, которая встречается довольно редко, но обычно вызывает серьезные затруднения.

Проект (листинг 5.13) содержит определение и реализацию знакомого класса Customer из предыдущего примера. В проекте определяется уникальное пространство имен, поскольку мы собираемся использовать его в других программах.

Листинг 5.13. Класс Customer из проекта /font>

' Пример (глава 5)

' Copyright ©2001 by Desaware Inc. All Rights Reserved

 Namespace MigratingBook.Chapter5.nbsp;

Public Mustlnherit r 

Public Name As String

Public MustOverride Function DefaultNet() As Integer

 Public Overridable Function Di splayTermsO As String DisplayName()

Console.WriteLine ("... is net " + CStr(DefaultNetO))

 End Function 

Sub DisplayName()

Console.WriteLine ("Customer is " + Name) 

End Sub

End ont>

End Namespace

Проект представляет собой библиотеку классов. В результате компиляции вы получаете DLL, на которую можно ссылаться из других сборок. Происходящее отчасти напоминает создание ActiveX DLL, совместно используемых разными приложениями, хотя в рассматриваемом случае не поддерживаются многие нетривиальные возможности компонентов, о которых будет рассказано ниже. Такой подход является простым и эффективным средством совместного использования программного кода.

Проект Customers2 идентичен проекту Customers 1, не считая того, что класс Customer определяется не внутри класса, а во внешней библиотеке.

А теперь рассмотрим следующую ситуацию: класс Government имеет собственную реализацию Branchlnfo. Что произойдет, если в один прекрасный день разработчик класса Customer добавит в него новую переопределяемую функцию Branchlnfo, не подозревая о том, что в производном классе уже определен метод с таким именем?

Базовые принципы полиморфизма предполагают, что при вызове Branchlnfo в коде базового класса должна вызываться функция производного класса. Это приводит к фатальным последствиям, поскольку разработчик базового класса никак не может предвидеть последствия от вызова метода производного класса. Несомненно, будет сделано совсем не то, что должна была сделать реализация базового класса.

Эта проблема называется «проблемой неустойчивости базового класса» и иногда приводит к весьма серьезным последствиями. В той или иной степени она проявляется и в C++ и в Java.

Проекты /font> и CustomersS показывают, как эта проблема решается в .NET. Чтобы увидеть, как возникает эта проблема и как она решается, вам придется воспроизвести ее вручную. Программный код примера соответствует лишь одному из моментов этого процесса.

Для начала приведите модуль ont> из проекта к виду, показанному в листинге 5.14.

Листинг 5.14. Объект Customer из проекта /font>

' Пример (глава 5)

' Copyright ©2001 by Desaware Inc. All Rights Reserved

 Namespace MigratingBook.Chapters.nbsp;

Public Mustlnherit

 Public Name As String

Public MustOverride Function DefaultNet() As Integer

 Public Overridable Function DisplayTerms() As String 

DisplayName()

Console.WriteLine ("... is net " + CStr(DefaultNetO)) 

' ConsoleWrite(" Branch is: ") ' Branchlnfo() 

End Function 

Sub DisplayName()

Console.WriteLine ("Customer is " + Name)

 End Sub

' Public Overridable Sub Branchlnfo() 

' Console.WriteLine("New Branchlnfo Function") 

' End Sub 

End ont>

End Namespace

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

Теперь восстановите закомментированные строки класса Customer в листинге 5.14. Откомпилируйте DLL и скопируйте ее в один каталог с только что построенным исполняемым файлом Customers3.

Метод Branchlnfo вызывается в двух местах: в базовом и производном классах. В соответствии с принципом полиморфизма все вызовы метода (даже через ссылку, относящуюся к базовому классу) должны передаваться в производный класс. Тем не менее результат оказывается иным:

Customer is Worst buys

Customer is Worst buys

... is net 30

Branch is: New Branchinfo Function

Press enter to continue:

Customer is Jim Smith

Customer is Jim Smith

... must pay immediately

Press enter to continue:

Customer is U.S.A.

Customer is U.S.A.

... will pay someday we hope

Legislative

Press enter to continue:

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

Даже в конечном объекте вызовы функции Branchlnfo из базового класса передаются реализации базового класса! Впрочем, при вызове Branchlnfo в функции производного класса вызывается реализация производного класса.

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

Попробуйте открыть проект Customers3. Неожиданно появляется сообщение об ошибке! VB .NET обнаруживает, что в производном классе имеется метод с именем, совпадающим с именем метода базового класса, но не имеющий атрибута Overrides. Теперь вам предстоит решить, следует ли переопределить метод базового класса (это нужно делать осторожно, хорошо понимая, что именно вы переопределяете), скрыть реализацию базового класса от производного класса при помощи ключевого слова Shadows1 или переименовать метод.

1 Ключевое слово Shadows не было реализовано в версии бета-1.

 

Видимость методов

Говоря о наследовании, остается рассмотреть последнюю тему — видимость и правила замещения. Большинство методов и свойств в приведенных выше примерах объявлялось с атрибутом Public. В производном классе такие методы и свойства полностью доступны.

Члены классов также могут объявляться с атрибутами Private, Friend и Protected.

Закрытые (Private) члены доступны только внутри класса. Производный класс не наследует их и не может обращаться к ним. Если бы в предыдущем примере функция Branchlnfo объекта Customer была объявлена с атрибутом Private, любые потенциальные конфликты с методом Branchlnf о класса Government были бы исключены.

Вероятно, атрибут Friend уже знаком вам по VB6. Дружественные члены классов наследуются производными классами, однако остаются видимыми только в пределах сборки.

Защищенные (Protected) члены классов относятся к числу нововведений VB .NET. К защищенным членам класса можно обращаться в производном классе, и они наследуются классами, производными от производных. Тем не менее к ним нельзя обращаться за пределами производного класса.

Допустим, у вас имеется класс А с защищенной функцией MyFunc:

Protected Sub MyFunc()

End Sub End

и от него создается производный класс В:

Inherits A

Public Sub MyPublicFunc()

End Sub 

End

В классе В метод MyFunc может вызываться напрямую: он был унаследован от класса А. Тем не менее, если попытаться объявить переменную типа В в другом месте, попытка вызова B  MyFunc завершится неудачей. Метод MyFunc доступен только в производном классе В; для внешних функций он остается невидимым. Метод также можно определить с атрибутами Protected Friend; при таком объявлении объединяются характеристики обоих атрибутов.

 

Итоги

В этой главе были описаны некоторые важные концепции.

  •  Архитектура .NET в значительной степени основана на наследовании. Все переменные и классы, объявленные в программе, являются производными от одного из классов иерархии (даже если это класс Object).
  •  Хотя ваши классы всегда являются производными от других классов, необходимость в дальнейшем наследовании возникает редко. Я бы даже порекомендовал завести полезную привычку — помечать классы атрибутом NotOverridable, чтобы предотвратить их возможное применение способом, на который они не рассчитаны.
  •  В большинстве программ VB повторное использование кода лучше всего реализуется на базе включения. Благодаря решению проблем с циклическими ссылками и перебором объектов VB .NET избавляет программистов от большинства трудностей с включением, существовавших в VB6.
  •  Если вы захотите создать объект, предназначенный для дальнейшего наследования, к его проектированию следует подходить с величайшей осторожностью. Хотя проблема неустойчивости базовых классов учтена в VB .NET, ее решение направлено лишь на то, чтобы обновленные компоненты можно было устанавливать без нарушения работы существующего кода. Вам все равно придется перепрограммировать производные классы при последующей компиляции.

Окончательный вердикт: хотя из всех новых возможностей VB .NET именно наследование сопровождалось наибольшей рекламной шумихой, скорее всего, вам никогда не придется создавать классы, предназначенные для дальнейшего наследования.

Назад   Вперёд

 


Инфо
Сайт создан: 20 июня 2015 г.
Рейтинг@Mail.ru
Главная страница