|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Метод
Tostring
Метод Tostring
повертає представлення поточного об'єкту в строковому форматі. Питання про те,
чи буде це уявлення зручним при відладці і для користувачів, залежить
від реалізації класу. За умовчанням Tostring повертає повне ім'я типу для заданого
об'єкту — наприклад, System. Object або Examplel.Programmer.
Постарайтеся звикнути до перевизначення Tostnng у ваших класах, щоб цей метод
повертав змістовніше строкове представлення класу. Наприклад, в класі Employee
з програми Employeetestl, приведеної в розділі 4, метод Tostring може виглядати
приблизно так: Public Overrides
Function Tostring() As String Dim temp
As String temp = Me.GetType.ToString()&
"my name is " & Me.TheName Return temp End
Function Зразковий
результат: Employeetestl+employeetestl+employee
my name is Tom
Кожен тип
.NET Framework представлений об'єктом Турі. Клас Турі містить безліч методів
з складними іменами — наприклад, метод Getmembers повертає інформацію про імена
всіх методів заданого класу. Метод Gettype класу Object повертає об'єкт
Турі, за допомогою якого можна отримати інформацію про тип під час виконання
програми. Зокрема, ця надзвичайно корисна можливість використовується для виконання
рефлексії (також використовується термін «ідентифікація типів
на стадії виконання»). До речі, простір імен Reflection
займає таке важливе місце в роботі .NET Framework, що воно автоматично імпортується
в кожен проект VS IDE. Щоб побачити,
як виконується рефлексія, включите в проект посилання на збірку System.Windows.Forms
і запустите приведену нижче програму. Коли через короткий проміжок часу
на екрані з'явиться запрошення, натисніть клавішу Enter. Продовжуйте натискати
Enter, і поступово в консольному вікні буде виведена інформація про всіх членів
класу Windows. Forms. Form, на основі якого будуються графічні застосування
в .NET. Зразковий вид вікна показаний на мал. 5.5.
Мал.
5.5. Інформація про членів класу Windows.Forms.Form, отримана за
допомогою рефлексії 1 Option
Strict On 2 Imports System.Windows.Forms 3 Module
Modulel 4 Sub Main() 5 Dim aform
As New Windows.Forms.Form() 6 Dim а
Type As Type 7 а Type = aform.GetType() 8 Dim
member As Object 9 Console.Writellne("This
displays the members of the Form class") 10 Console.WriteLineC'Press
enter to see the next one.") 11 For Each
member In atype.GetMembers 12 Console.ReadLine() 13 Console.
Write(member.ToSthng) 14
Next 15 Console.WriteLine("Press
enter to end") 16 Console.ReadLine() 17 End
Sub 18 End Module У рядках 6
і 7 ми отримуємо об'єкт Турі для класу Windows. Forms. Form. Потім, оскільки метод
Getmembers класу Турі повертає колекцію об'єктів Memberlnfo, що описують
члени класу, програма просто перебирає всі елементи колекції в рядках 11-14.
У програмуванні, як і в сучасній науці:
Але
найважливіше правило клонування формулюється так:
Наступний
приклад наочно показує, що мається на увазі під цим попередженням. Масиви
VB .NET на відміну від масивів Vb6 є об'єктами.
Допустимо, ми намагаємося клонувати об'єкт класу, одне з полів якого є
масивом: 1 Public
Class Embeddedobjects 2 Private m_data()
As String 3 Public Sub
New(Byva1 anarray() As String) 4 m_data
= anarray 5 End
Sub 6 Public Sub
Oisplaydata() 7 Dim
temp As String 8 For
Each temp In m_data 9 Console.WriteLine(temp) 10
Next 11 End
Sub 12 Public Sub
Changedata(Byval newdata As String) 13 m_data(0)
= newdata 14 End
Sub 15 Public Function
Clone() As Embeddedobjects 16 Return Ctype(Me.MemberwiseClone.
Embeddedobjects) 17 End
Function 18 End Class Виконаєте
наступну процедуру Sub Main: Sub Main() Dim anarray()
As String ={"HELLO"} Dim а As New
Embeddedobjects(anarray) Console.WriteLinet"Am
going to display the data in object а now!") а.DisplayData() Dim b As
Embeddedobjects b =a.Clone() Dim
newdata As String ="GOODBYE" b.ChangeData(newdata) Console.WriteLine("Am
going to display the data in object b now!") b.DisplayData() Console.WriteLine("Am
going to re-display the data in а" & _ "after making а change to object b!!!") а.DisplayData() Console. Readline()
End Sub
Мал.
5.6. Метод
Memberwiseclose не працює Як видно
з мал. 5.6, результат вийшов вельми несподіваним: зміни клона відбиваються
на початковому об'єкті! Що відбувається
в даному прикладі? Чому метод Memberwiseclone не працює, як задумано? Чому
зміни в об'єкті b відбиваються на об'єкті а? Тому що в рядках 2 і 4 класи
Embeddedobjects як значення поля, що задається в конструкторі, використовується
масив. Масиви є змінними об'єктами; як було показано в розділі
3, з цього виходить, що вміст масиву може змінюватися навіть при передачі
за значенням (Byval). Стан внутрішнього масиву змінюється в рядках 12-14
класу Embeddedobjects. Оскільки об'єкт і псевдоклон зв'язані посиланням на масив
m_data, зміни клона відбиваються на початковому об'єкті. Вирішення цієї
проблеми розглядається в розділі «Icloneable» цього розділу. А поки
ми просто вкажемо, що справжній клон (іноді званий глибокою копією)
створює клони всіх полів об'єкту, при необхідності виконуючи рекурсивне
кло-нірованіє. Наприклад, якщо одне з полів класу є об'єктом і містить
ще один внутрішній об'єкт, процес клонування повинен опуститися на два рівні
в глиб.
Нарешті, як засіб додаткового захисту розробники .NET Framework оголосили
Memberwiseclone захищеним методом класу Object. Як було показано вище, це
означає, що Memberwi seci one може викликатися тільки з похідних класів.
Код за межами похідного класу не може клонувати об'єкти за допомогою
цього небезпечного методу. Також звернете увагу на те, що Memberwi secione
повертає тип Object, тому в рядку 1б класу Embeddedobjects доводиться
використовувати функцію Стуре.
Проблема
нестійкості базових класів і контроль версії
Проблема несумісності
компонентів добре відома всім, кому доводилося програмувати для
Windows. Зазвичай вона виступає у формі так званого кошмару DLL (DLL Hell) —
програма використовує певну версію DLL, а потім установка нової версії компоненту
порушує роботу програми. Чому? Причини можуть бути разнимі, від очевидних
(випадкове виключення функції, що використалася в програмі) до вельми
нетривіальних (наприклад, зміна типу повертаного значення у функції). У
будь-якому випадку все зводиться до варіацій на одну тему — при зміні відкритого
інтерфейсу коди, від якої залежить ваша програма, програма не може використовувати
нову версію замість старої, а стара версія вже стерта. У більшості об'єктно-орієнтованих
мов спадкоємство зв'язане з потенційною загрозою працездатності вашої
програми із-за несумісності компонентів. Програмістові залишається лише сподіватися
на те, що відкриті і захищені члени класів-попередників в 1 ієрархії спадкоємства не
змінюватимуться, таким чином, що це порушить ра- ботоспособность
їх програм. Ця ситуація називається проблемою нестійкості базових класів.
Спадкоємство часто перетворює наші програми на якусь подібність карткового
будиночка — спробуйте витягнути нижню карту, і вся споруда розвалиться. Проблему нестійкості
базових класів бажано розглянути на конкретному прикладі. Розмістите
приведене нижче визначення класу Payabl eentity в отдель-ной^бібліотеке і
відкомпілюйте його в збірку з ім'ям Payableentity Example командою Build (щоб
задати ім'я збірки, клацніть правою кнопкою миші на імені проекту у вікні рішення,
виберіть в контекстному меню команду Properties і введіть потрібні значення в
діалоговому вікні). Якщо ви не використаєте архів з прикладами, що додається до
книги, запам'ятаєте, в якому каталозі був побудований проект: Public Mustlnherit Class Payableentity Private m_name As String Public Sub New(Byval
thename As String) m_name =theName End
Sub Public Readonly
Property Thename()As String Get Return m_name End Get End
Property Public Mustoverride Property TAXID()As String
End Class Після побудови
DLL закрийте рішення. Допустимо, ви вирішили включити в клас Employee новий спосіб отримання адреси, залежний від базового класу Payableentity; при цьому слід пам'ятати, що клас використовуватиметься тільки у формі, що відкомпілювалася. Для цього необхідно включити посилання на збірку, що містить цей проект (знаходиться в підкаталозі \bin того каталога, в якому була побудована DLL Payableentityexample). Зразковий код класу Empl oyee приведений нижче. Звернете увагу на рядок, виділений жирним шрифтом, в якій клас оголошується похідним від абстрактного класу, визначеного в збірці Payableentityexample. Public
Class Employee ' Простір імен називається Payableentityexample. ' тому повне ім'я класу записується у вигляді Payableentityexample.PayableEntity! Inherits Payableentityexample.Employee Private m_name As String Private m_salary As Decimal Private m_address As String Private m_taxid As String Private Const
LIMIT As Decimal = 0.1d Public Sub New(Byval thename As String Byval cursalary As Decimal Byval TAXID
As String) Mybase.New(thename) m_name =
thename m_salary
= cursalary m_taxid = TAXID End
Sub Public Property Address()As String Get
Return m_address End Get Set(Byval Value
As String) m_address = Value End Set End
Property Public Readonly
Property Salary()As Decimal Get Return m_salary « End Get End
Property Public
Overrides Property TAXIDO As String Get Return m_taxid End
Get Setcbyval Value As String) If Value.Length
<> 11 Then ' Див. розділ
7 Else m_taxid = Value End If End Set End Property End
Class Процедура
Sub Main виглядає так: Sub Main() Dim torn As
New Employeec'tom". 50000) tom.Address
="901 Grayson" Console.WriteCtom.TheName
& "lives at " & tom.Address) Console. Readline() End Sub Результат
показаний на мал. 5.7. Програма працює саме так, як передбачалося.
Мал.
5.7. Демонстрація нестійкості базових класів (контроль версії відсутній) Програма
компілюється у виконуваний файл Versiomngl.exe, все йде чудово. Тепер припустимо,
що клас Payableentity був розроблений незалежною фірмою. Геніальні розробники
класу Payableentity не бажають опочивати на лаврах! Піклуючись про благо користувачів,
вони включають в свій клас об'єкт з адресою і розсилають новий варіант
DLL. Початковий текст вони тримають в секреті, але ми його приводимо нижче. Зміни
в конструкторі виділені жирним шрифтом: Imports Microsoft.Vi sualbasic.Control Chars Public
Class Payableentity Private m_name As String Private
m_address As Address Public Sub New(Byval thename As String,byval theaddress As Address) m_name = thename m_address = theaddress End
Sub Public Readonly
Property Thename()As String Get Return
m_name End Get End Property Public Readonly
Property Theaddress() Get Return m_address.DisplayAddress End Get End Property End Class Public
Class Address Private
m_address As String Private
m_city As String Private
m_state As String Private
m_zip As String Public Sub New(Byval
theaddress As String.ByVal thecity As String. Byval thestate As String.ByVal thezip As String) m_address = theaddress m_city = thecity m_state = thestate m_zip = thezip End Sub Public Function
Displayaddress() As String Return m_address
& Crlf & m_city & "." & m_state _ &crlf & m_zip End Function End
Class Перед вами приклад
рідкісної халтури. В процесі «удосконалення» автори вмудрилися
втратити початковий конструктор класу Payableentity! Звичайно, такого бути
не повинно, але раніше подібні катастрофи все ж таки траплялися. Стара DLL встановлювалася
на жорсткий диск користувача (зазвичай в каталог Windows\system). Потім виходила
нова версія, встановлювалася поверх старої, і цілком благополучна програма
Versioningl переставала працювати (а як їй працювати, якщо змінився конструктор
базового класу?).
Звичайно, проектувальники базових класів так поступати не повинні, проте на практиці
бувало всяке. Але спробуйте відтворити цей приклад .NET, і відбудеться справжнє
диво: ваша стара програма нормально працюватиме, тому що вона використовує початкову
версію Payabl eenti ty з бібліотеки, що зберігається в каталозі \bin вирішення
Versioningl. Схема контролю
версії в .NET дозволяє розробникам компонентів доповнювати свої базові класи
новими членами (хоча на практиці робити цього не рекомендується). Така можливість
зберігається навіть в тому випадку, якщо імена нових членів збігаються з іменами членів,
включених вами в похідний клас. Старий виконуваний файл, створений
на базі похідного класу, продовжує працювати, оскільки він не використовує
нову DLL. Втім,
це не зовсім вірно: він дійсно продовжує працювати — до тих пір, поки
ви не відкриєте початковий текст додатку Versioningl в VS .NET, створите посилання
на DLL Payableentityexample і спробуєте побудувати додаток Versioningl наново.
Компілятор видасть повідомлення про помилку: C:\book to comp\chapter 5\versioningl\versioningl\modu1el.vb(21): No argument specified or non-optional parameter 'theaddress' of 'Public Sub New(thename As String,theaddress As Payableentityexample.Address)'. Отже, як
тільки ви завантажите старий початковий текст похідного класу і створите
посилання на нову DLL, вам не вдасться відкомпілювати програму до виправлення
тієї несумісності, на яку вас прирекли розробники базового класу. Перш ніж
завершити цей розділ, ми хочемо роз'яснити ще одну обставину. Виключення
конструктора з класу і заміна його іншим конструктором — вельми груба і очевидна
помилка. Чи здатний механізм контролю версії .NET врятувати від інших, менш тривіальних
помилок? Так, здатний.
Розглянемо найпоширеніше (хоча досить тривіальний) джерело помилок несумісності
при використанні спадкоємства. Є похідний клас Derived, залежний
від базового класу Parent. У клас Derived включається новий метод Parselt (у
наступному прикладі він просто розділяє рядок по словах і виводить кожне слово
в окремому рядку): Imports Microsoft.VisualBasic.ControlChars
Module Modulel Submain() Dim myderived As New Oerived() myderived.DisplayIt 0 Console.ReadLine() End Sub End Module Public
Class Parent Public Const MY STRING As String ="this is а test" Public Overridable
Sub Displaylt() Console.WriteLine(My_string) End Sub End
Class Public Class Derived Inherits Parent Public Overrides
Sub Displaylt() Console.WriteLine(Parseit(Mybase.MY_STRING)) End
Sub Public Function Parselubyval astring As String) Dim tokens() As String ' Розбити рядок по пропусках tokens - astring.Split(Chr(32)) Dim temp
As String '
Об'єднати в один рядок, вставляючи між словами ' комбінацію
символів Cr/lf temp = Join(tokens.CrLf) Return temp End Function End Class End Module Результат
показаний на мал. 5.8.
Мал.
5.8. Просте розбиття
рядка по словах Тепер уявіть собі,
що клас Parent розповсюджується не у вигляді початкових текстів, а у формі, що
відкомпілювалася. Версія 2 класи Parent містить власну версію Parselt, яка широко
використовується в її коді. Відповідно до принципу поліморфізму при зберіганні об'єкту
типу Den ved в об'єктній змінній типу Parent виклики Displaylt повинні використовувати
метод Parselt класу Derived замість методу Parselt базового класу. Проте
тут виникає маловірогідна, але теоретично можлива проблема. У нашому
сценарії код класу Parent, що використовує свою версію функції Parselt,
не знає, як функція Parselt реалізована в класі Derived. Поліморфний виклик
версії Parselt похідного класу може порушити які-небудь умови, необхідні
для роботи базового класу. У цій ситуації засобу контролю
версії VB .NET теж творять дива: код базового класу Parent,
що відкомпілювався, продовжує використовувати свою версію Parselt завжди, навіть
не дивлячись на те, що при зберіганні об'єктів Derived в змінних типу
Parent поліморфізм привів би до виклику неправильної версії методу. Як згадувалося
в попередньому прикладі, при відкритті коди Derived в Visual Studio компілятор повідомляє,
що для усунення неоднозначності в оголошення методу Parselt похідного
класу слід включити ключове слово Override або Shadows.
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||