Нижче приведені процедури A,B і C, що обговорювалися раніше, втілені в новому синтаксисі Object Pascal:
type
ESampleError = class(Exception);
var
ErrorCondition: Boolean;
procedure C;
begin
writeln('Enter C');
if (ErrorCondition) then
begin
writeln('Raising exception in C');
raise ESampleError.Create('Error!');
end;
writeln('Exit C');
end;
procedure B;
begin
writeln('enter B');
C;
writeln('exit B');
end;
procedure A;
begin
writeln('Enter A');
try
writeln('Enter A''s try block');
B;
writeln('After B call');
except
on ESampleError do
writeln('Inside A''s ESampleError handler');
on ESomethingElse do
writeln('Inside A''s ESomethingElse handler');
end;
writeln('Exit A');
end;
begin
writeln('begin main');
ErrorCondition := True;
A;
writeln('end main');
end.
При ErrorCondition = True програма видасть:
begin main
Enter A
Enter A's try block
enter B
Enter C
Raising exception in C
Inside A's ESampleError handler
Exit A
end main
Можливо вас здивувала декларація типу 'ESampleError =class' замість '=object'; це ще одне нове розширення мови. Delphi уводить нову модель об'єктів, доступну через декларацію типу '=class'. Опис нової об'єктної моделі дається в інших уроках. Тут же досить сказати, що виняткові ситуації (exceptions) є класами, частиною нової об'єктної моделі.
Процедура C перевіряє наявність помилки (у нашому випадку це значення глобальної перемінної) і, якщо вона є (а це так), C викликає(raise) виняткову ситуацію класу ESampleError.
Процедура A поміщає частину коду в блок try..except. Перша частина цього блоку містить частину коду, аналогічно конструкції begin..end. Ця частина коду завершується ключовим словом except, далі випливає один чи більш оброблювачів виняткових ситуацій on xxxx do yyyy, далі може бути включений необов'язковий блок else, уся конструкція закінчується end;. У конструкції, що призначає визначену обробку для конкретної виняткової ситуації (on xxxx do yyyy), після резервного слова on указується клас виняткової ситуації, а після do випливає власне код обробки даної помилки. Якщо виникла виняткова ситуація підходить по типу до зазначеного послу on, то виконання програми переходить сюди (на код після do). Виняткова ситуація підходить у тім випадку, якщо вона того ж класу, що зазначено в on, або є його нащадком. Наприклад, у випадку on EFileNotFound оброблятися буде ситуація, коли файл не знайдений. А у випадку on EFileIO - усі помилки при роботі з файлами, у тому числі і попередня ситуація. У блоці else обробляються всі помилки, не оброблені до цього.
Приведені в прикладі процедури містять код (рядок з writeln), що відображає шлях виконання програми. Коли C викликає exception, програма відразу переходить на оброблювач помилок у процедурі A, ігноруючи частину коду, що залишилася, у процедурах B і C.
Після того, як знайдений придатний оброблювач помилки, пошук закінчується. Після виконання коду оброблювача, програма продовжує виконуватися з оператора, що коштує після слова end блоку try..except (у прикладі - writeln('Exit A')).
Конструкція try..except підходить, якщо відомо, який тип помилок потрібно обробляти в конкретній ситуації. Але що робити, якщо потрібно виконати деякі дії в будь-якому випадку, відбулася чи помилка ні? Це той випадок, коли знадобиться конструкція try..finally.
Розглянемо модифіковану процедуру B:
procedure NewB;
var
P: Pointer;
begin
writeln('enter B');
GetMem(P, 1000);
C;
FreeMem(P, 1000);
writeln('exit B');
end;
Якщо C викликає виняткову ситуацію, то програма вже не повертається в процедуру B. А що ж з тими 1000 байтами пам'яті, захопленими в B? Рядок FreeMem(P,1000) не виконається і Ви втратите шматок пам'яті. Як це виправити? Потрібно ненав'язливо включити процедуру B у процес, наприклад:
procedure NewB;
var
P: Pointer;
begin
writeln('enter NewB');
GetMem(P, 1000);
try
writeln('enter NewB''s try block');
C;
writeln('end of NewB''s try block');
finally
writeln('inside NewB''s finally block');
FreeMem(P, 1000);
end;
writeln('exit New');
end;
Якщо в A помістити виклик New замість B, то програма виведе повідомлення в такий спосіб:
begin main
Enter A
Enter A's try block
enter NewB
enter NewB's try block
Enter C
Raising exception in C
inside NewB's finally block
Inside A's ESampleError handler
Exit A
end main
Код у блоці finally виконається при будь-якій помилці, що виникла у відповідному блоці try. Він же виконається й у тому випадку, якщо помилки не виникло. У будь-якому випадку пам'ять буде звільнена. Якщо виникла помилка, то спочатку виконується блок finally, потім починається пошук придатного оброблювача. У штатній ситуації, після блоку finally програма переходить на наступне пропозицію після блоку.
Чому виклик GetMem не поміщений усередину блоку try? Цей виклик може закінчитися невдало і викликати exception EOutOfMemory. Якщо це відбулося, то FreeMem спробує звільнити пам'ять, що не була розподілена. Коли ми розміщаємо GetMem поза ділянкою, що захищається, то припускаємо, що B зможе одержати потрібну кількість пам'яті, а якщо ні, те більш верхня процедура одержить повідомлення EOutOfMemory.
А що, якщо потрібно в B розподілити 4 області пам'яті за схемою ус-чи-нічого? Якщо перші дві спроби удалися, а третя провалилася, то як звільнити захоплену область пам'ять? Можна так:
procedure NewB;
var
p,q,r,s: Pointer;
begin
writeln('enter B');
P := nil;
Q := nil;
R := nil;
S := nil;
try
writeln('enter B''s try block');
GetMem(P, 1000);
GetMem(Q, 1000);
GetMem(R, 1000);
GetMem(S, 1000);
C;
writeln('end of B''s try block');
finally
writeln('inside B''s finally block');
if P <> nil then FreeMem(P, 1000);
if Q <> nil then FreeMem(Q, 1000);
if R <> nil then FreeMem(R, 1000);
if S <> nil then FreeMem(S, 1000);
end;
writeln('exit B');
end;
Установивши спершу покажчики в NIL, далі можна визначити, чи успішно пройшов виклик GetMem.
Обидва типи конструкції try можна використовувати в будь-якім місці, допускається вкладеність будь-якої глибини. Виняткову ситуацію можна викликати усередині оброблювача помилки, конструкцію try можна використовувати усередині оброблювача виняткової ситуації.