|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Загальні члени класів
Закриті
загальні поля класів у поєднанні з ReadOnly-свойствами дуже зручні, але цим
сфера застосування ключового слова Shared не вичерпується. У класі можна оголошувати
загальні властивості і методи. Як було показано на прикладі класу Math, при зверненні
до загальних засобів класу указується або ім'я класу, або ім'я конкретного екземпляра.
Допустимо, в клас Employee включається загальна функція Calcul atefica, залежна
від двох відкритих констант: Public Const Fica_limit As Integer = 76200 Public Const
Fica_percentage As Decimal = 0.062d Функція CALCULATEFICA
виглядає так: Public Shared Function CALCULATEFICA(Byval asalary As Decimal) As Decimal If asalary >
Fica_limit Then Return Fica_limit * Fica_percentage Else Return asalary * Fica_percentage End If End Function Загальні члени
класу можуть використовуватися без створення екземплярів Empl oyee, тільки по імені
класу. Приклад: System.Console.WriteLine(Employee. CALCULATEFICA(100000)) З іншого
боку, метод мджно викликати і для конкретного екземпляра Employee: System.Console.WriteLine (Tom.CalculateFICA (Tom.GetSalary()) Конструктори теж можна оголошувати загальними, для цього в оголошення методу New включається
ключове слово Shared. Загальні конструктори:
Отже,
при створенні екземпляра класу оператором New викликається відповідний
метод-конструктор New з визначення класу (також може бути викликаний загальний конструктор, якщо
він є). Версія конструктора вибирається відповідно до типу переданих параметрів.
Конструктор можна розглядати як аналог події Class_initiall
ze в Vb6. Не у кожному
класі визначається відкритий конструктор. Більш того, в деяких ситуаціях
всі конструктори класу оголошуються закритими і екземпляри створюються тільки
загальними методами. Конструктор оголошується закритим в одному з наступних випадків:
Після того,
як об'єкт буде створений оператором New, ви не зможете змінити його стан
повторним викликом New. Приклад: Dim Tom As New Employeec'tom ", 100000) Tom = New Employee("Tom
". 125000) У цьому фрагменті
створюються два разних об'єкту Empl oyee, причому після привласнення в другому
рядку перший об'єкт Той втрачається. Іноді це відповідає намереніям
програміста, іноді — ні. Наприклад, якщо ідентифікатор працівника зберігається
в загальній змінній Empl oyeeid, то другий рядок привласнить другому об'єкту Той
ідентифікатор на 1 більше первинного. Так або інакше, наступний фрагмент
свідомо неможливий: Dim Tom As New Employee("Tom ", 100000) Dim Tom As New
Employee("Tom ", 125000) Компілятор
видає наступне повідомлення про помилку: The local variable
'Tom' is defined multiple times in the same method.
У VB
.NET об'єкти не вмирають «природною смертю»; у якомусь сенсі
вони поступово «сходять у небуття» з часом. Головна відмінність від
попередніх версій VB полягає в тому, що ви не можете явно звільнити пам'ять,
займану об'єктом. Вбудований складальник сміття коли-небудь відмітить, що ці блоки
пам'яті не використовуються в програмі, і автоматично звільнить їх. Автоматична
збірка сміття робить сильний вплив на програмування в VB .NET. Зокрема,
збірку сміття слід розглядати як повністю автоматизований процес,
на який ви абсолютно не можете вплинути. Хоча в програмі
можна провести примусову збірку сміття викликом методу System. GC. Collect(),
вважається, що це не відповідає хорошому стилю програмування .NET. Ми
рекомендуємо завжди покладатися на автоматичну збірку сміття. Пригадаєте,
що в колишніх версіях VB в кожному класі існувала подія Termi nate, яке
гарантовано викликалося в той момент, коли кількість посилань зменшувалася
до 0 (у термінології ООП це називається детермінованим завершенням). У
VB .NET (як би ви до цього не відносилися) підтримується тільки недетерміноване
завершення, з чого виходить, що ви не можете розраховувати на те, що якийсь
аналог події Termi nate буде викликаний в певний момент часу. Більш
того, не гарантовано навіть те, що він взагалі буде коли-небудь викликаний!
Виникає
питання: якщо раніше ми проводили дєїніциалізацию класу в події Terminate,
як же це робиться зараз? Для вирішення цієї проблеми в VB .NET існує дуже
важливе правило: Якщо ваш клас
повинен звільняти які-небудь зовнішні ресурси, окрім звичайної пам'яті (наприклад, підключення
до бази даних, графічні контексти, файлові маніпулятори і т.. д.),
він повинен містити метод з ім'ям Di spose, що викликається із зовнішньої
коди. Ми
повернемося до методу Dispose при розгляді інтерфейсу Idisposabl e в розділі 5. А
поки досить сказати, що будь-яке графічне застосування — навіть найпростіше,
на зразок продемонстрованого в розділі 1, — відноситься до категорії програм, в
яких необхідний метод Dispose. Це пов'язано з тим, що графічні програми
захоплюють так звані графічні контексти, які мають бути звільнені для
повернення ресурсів в систему (графічні контексти не є блоками пам'яті,
тому автоматична збірка сміття в даному випадку не допоможе). Тепер
стає ясно, чому в код, що автоматично згенерував, приведений в розділі
1, входить виклик Dispose. Недетерміноване завершення належить до самих
неоднозначних нововведень .NET, проте автоматична збірка сміття є
невід'ємною частиною .NET. Розробникам за всього бажання не вдалося б зберегти
колишній, детермінований варіант управління пам'яттю і забезпечити сумісність .NET.
Крім того, механізму, використаному в старих версіях VB (підрахунок посилань),
властиві проблеми з витоком пам'яті, викликаної існуванням циклічних посилань,
коли об'єкт А посилається на об'єкт В і навпаки, як показано на мал. 4.8.
Мал.
4.8. Два різновиди
циклічних посилань
Традиційно
в об'єктно-орієнтованих мовах виникали немало проблем з простими типами
даних — такими, як звичайні цілі числа. Річ у тому, що в об'єктно-орієнтованій
мові всі дані мають бути об'єктами. З іншого боку, створення об'єкту зв'язане
з певними витратами на виконання службових операцій (таких, як виділення
блоку пам'яті для об'єкту). Обробка повідомлення «скласти» також поступається
за швидкістю простою математичній операції складання і так далі Варто додати,
що в мовах з автоматичною збіркою сміття якийсь час витрачається на
знищення невживаних об'єктів. Ранні об'єктно-орієнтовані мови пішли по найпрямолінійнішому шляху. Скажімо, в Smalltalk всі дані інтерпретувалися як об'єкти. В результаті такі мови працювали повільніше, ніж мови з розділенням примітивних типів і об'єктів. З іншого боку, подібне розділення приводило до ускладнення програм, оскільки програмний код, що працював з числами, доводилося відокремлювати від коди, що працювала з об'єктами. Щоб інтерпретувати число в об'єктному контексті, його доводилося «завертати» в об'єкт. Наприклад, в Java збереження числа в еквіваленті динамічного масиву виглядало приблизно так: anarraylist.Add(Integer(5)); Число 5 «заверталося»
в об'єкт Integer. Такі програми погано читалися і поволі працювали. У .NET Framework
були об'єднані кращі сторони обох рішень. У загальному випадку числа інтерпретуються
як примітивні типи, але при необхідності вони автоматично інтерпретуються
як об'єкти. Таким чином, для звичайного числового літерала можна викликати метод
або занести його в хэш-таблицу без додаткових зусиль. Це називається автоматичною
упаковкою (boxing); зворотний процес називається розпаковуванням (unboxing). Для нас,
програмістів, з цього витікає важливе слідство: хоча в VB .NET всі дані
є об'єктами, не кожна змінна в програмі містить маніпулятор блоку
пам'яті і створюється оператором New. Зрозуміло, ніщо не дається дарма: програмістові
доводиться пам'ятати про відмінності між структурними і посилальними типами. Перше,
найбільш очевидна відмінність полягає в тому, що нові екземпляри структурних
типів створюються без ключового слова New. Вам не доведеться (та і не вдасться) використовувати
конструкції виду Dim а As New Integer(5).
Серйозніша відмінність пов'язана з передачею змінних процедурам за значенням. Як було
сказано вище, при передачі змінного об'єкту за значенням процедура може
змінити стан об'єкту. Передані за значенням структурні типи поводяться цілком
традиційно — всі зміни втрачаються при виході з викликаної процедури або функції
(іноді це називається структурною семантикою на відміну від посилальної
семантики). Всі числові типи VB .NET є структурними
типами; до цієї ж категорії відноситься і такий тип, як дата. Як буде
показано нижче, VB .NET дозволяє визначити призначені для користувача структурні типи, якщо
з міркувань швидкодії ви хочете звести до мінімуму витрати на роботу
з об'єктами або ж вважаєте за краще працювати з об'єктами, що володіють
структурною семантикою. Function Isvaluetype(Byval foo As Object) As Boolean If
Typeof (foo) Is
System.ValueType Then Return
True Else Return False End If End Function Для об'єктів
структурного типу оператора Equal s завжди повертає True, якщо структурні
об'єкти містять однакові дані. Синтаксис виклику виглядає так: а..Fquals(b) Врахуйте, що
для посилальних типів цей принцип в загальному випадку не виконується. Наприклад, два
масиви можуть містити однакові елементи, але рівними при цьому вони не вважаються. У VB
.NET структурні типи діляться на дві категорії: структури і перераховувані типи.
Ми почнемо з перераховуваних типів, а потім перейдемо до структур, які
є «полегшеними» варіантами об'єктів.
Перераховувані
типи зазвичай використовуються для визначення набору іменованих цілочисельних констант.
При визначенні перераховуваного типу використовується пара ключових слів Enum-end
Enum разом з модифікатором доступу. Перераховуваний тип може містити тільки
цілочисельні типи ніби Integer або Long (тип Char недопустимий). Наприклад, в
наступному фрагменті визначається відкритий перераховуваний тип з ім'ям Bonusstructure: Public
Enum Bonusstructure None =
0 Firstlevel = 1 Secondlevel = 2 End Enum Після цього
в будь-якому місці програми можна оголосити змінну типу Bonusstructure: Dim bonuslevel
As Bonusstructure При роботі
з перераховуваними типами, як і з іншими структурними типами, ключове слово
New не використовується. Визначивши в проекті перераховуваний тип, ви можете
використовувати конструкції вигляду Bonus =Tom.Sales
* bonuslevel.SecondLevel Оскільки
перераховувані типи неявно інтерпретуються як загальні, в посиланнях на них можна
указувати ім'я перераховуваного типу замість імені змінної: Public Function
Calcu1atebonus(Byval thesales As Decimal) As Decimal Return thesales * Bonusstructure.SecondLevel End Function Одним з
традиційних недоліків перераховуваних типів була відсутність зручних засобів
для отримання імені за значенням, що утрудняло відладку програм. У класі Enum,
базовому для всіх перераховуваних типів, визначені дуже корисні методи для отримання
подібної інформації. Наприклад, наступна команда повертає
рядок Firstlevel : Bonusstructure.GetName(bonuslevel
.GetType.l) Даний фрагмент виводить
всі імена, що входять в перераховуваний тип: Dim enumnames
As String().s As String enumnames = Bonusstructure.GetNames(bonuslevel.GetType) For
Eachs In enumnames System.Console.WriteLine(s)
Next
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||