Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2019年04月24日] [更新 2019年04月24日] Delphi 一般・その他
   
try - finally - end 構文
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0920]







try - finally - end 構文


try - finally - end 文の動作確認用の簡単なテストサンプルです.






01_必ず必要な処理を finally ブロックに記述する


try - finally - end という構文を使用すると,try ブロック (try と finally の間) のコードを実行した後に,finally ブロック (finally と end の間) のコードを必ず実行してくれます.
例えば,以下のように,生成したオブジェクトの解放や破棄の処理の目的に利用できます.



図1
try - finally - end 構文
  • finally ブロックが必ず実行されることを利用して,生成したオブジェクトの解放処理等に使用されている

リスト1
try - finally - end 構文の使用例
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    //
  finally
    // 生成したオブジェクトの解放破棄を確実に実行
    FreeAndNil(LStrList);
    Memo1.Lines.Add('finally - block');
  end;

  Memo1.Lines.Add('end procedure');
end;


実行する処理に何も問題がなければ,次のようにも書けますが,生成したオブジェクト等の解放や破棄の処理等は finally ブロックで行うと決めておけば,コードの可読性が高くなります.



procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
begin
  LStrList := TStringList.Create;

  // 問題が発生する可能性がない処理

  FreeAndNil(LStrList);
end;


try ブロックで例外が発生しても finally ブロックは実行されます.finally ブロックよりも下のコードは実行されませんが,finally ブロックが実行されますから,以下のように,生成したオブジェクトの解放処理は行われることになります.
このコードでは,例外が発生したら処理は終了ということになります.



図2
例外が発生してダイアログが表示される例
図3
例外が発生しても finally ブロックは実行される
  • finally ブロックよりも後は実行されない


リスト2
例外が発生しても,finally ブロックは実行される
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LValueA  : Integer;
  LValueB  : Integer;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    LValueA := 0;
    LValueB := 50000 div LValueA;
  finally
    // 例外が発生してもここは実行される
    FreeAndNil(LStrList);
    Memo1.Lines.Add('finally - block');
  end;

  // 例外が発生した場合,ここは実行されない
  Memo1.Lines.Add('end procedure');
end;


[備考]
この例では,try - finally - end という 構文は例外処理用の 1 つの構文という見方ができます.一方,次項のように,必ず実行したいコードを記述するための構文という見方もできます.
いずれにしても,finally ブロックが必ず実行されるという点では同じです.




02_try - finally ブロック内で Exit する場合


try ブロックで Exit を実行しても finally ブロックは実行されます.この時,finally ブロックよりも下のコードは実行されませんから,以下のように,条件を満たさない場合は処理を中止するというような場合に利用できます.




図4
Exit で処理を中止
  • try ブロック内で Exit しても finally ブロックは実行される
  • finally ブロックよりも後は実行されない

リスト3
処理条件を満たさない時は処理を中止
try 内で Exit すると finally ブロックよりも後は実行しないが,finally ブロックは実行される
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LValueA  : Integer;
  LValueB  : Integer;
  LFlag    : Boolean;
begin
  Memo1.Lines.Clear;

  LFlag := False;
  LStrList := TStringList.Create;
  try
    LValueA := 200;
    // 条件を満たさない場合は Exit する場合
    if LValueA <= 0  then Exit;
    if LValueA > 100 then Exit;

    LValueB := 50000 div LValueA;

    LFlag := True;
  finally
    if not LFlag then begin
      MessageBox(Handle, '条件が不十分なので中止します', '情報', MB_ICONINFORMATION);
    end;
    FreeAndNil(LStrList);
    Memo1.Lines.Add('finally - block');
  end;

  // 条件を満たさない場,合ここは実行されない
  Memo1.Lines.Add('end procedure');
end;


次のコードは,条件を満たす場合に次の条件を判定するような場合です.


リスト4
処理条件を満たす時だけ処理を実行する例
条件文関係のネストが深くなる可能性がある
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LValueA  : Integer;
  LValueB  : Integer;
  LFlag    : Boolean;
begin
  Memo1.Lines.Clear;

  LFlag := False;
  LStrList := TStringList.Create;
  try
    LValueA := 200;
    // 条件を満たす場合に次の条件をチェックするような場合
    // 条件判定が多くなるとネストが深くなる
    if LValueA > 0 then begin
      if LValueA <= 100 then begin
        LValueB := 50000 div LValueA;
        LFlag := True;
      end;
    end;
    // ここで Exit してもよい
    // if not LFlag then Exit;
  finally
    if not LFlag then begin
      MessageBox(Handle, '条件が不十分なので中止します', '情報', MB_ICONINFORMATION);
      // finally ブロック内からは Exit できない
      // Exit できてしまうと finally ブロックは必ず実行されるという原則に反してしまう
      // したがって確実に以下の FreeAndNil が処理できる
      // Exit;
    end;
    FreeAndNil(LStrList);
    Memo1.Lines.Add('finally - block');
  end;

  // try ブロック内で Exit していないので以下も実行される
  Memo1.Lines.Add('end procedure');
  if not LFlag then Exit;

  // LFlag が True の時の処理
end;




03_try - except - end 文と try - finally - end 文による例外処理


try - except - end の構文と try - finally - end の構文を組み合わせて使用すると,より柔軟性のある例外処理が可能です.
try - except - end 構文は例外を捕捉するために使用する構文です.try - except の間で発生した例外を捕捉して,その例外をクリアして except ブロックのコードを実行します.

下に示すコードは前述の「ゼロによる除算」の例外が発生するサンプルですが,try - except 構文を使用しているので,例外がクリアされ,例外のダイアログは現れません.
このコードは finallly ブロックも実行されますが,finally ブロックのコードも実行されます.したがって,例外が発生しても処理を続けたい場合の例と言えます.



図5
except と- finally を使用した例外処理-
  • 例外が発生しても処理を続ける例

リスト5
except と- finally を使用した例外処理.例外が発生しても処理を続ける例
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LValueA  : Integer;
  LValueB  : Integer;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    try
      LValueA := 0;
      LValueB := 50000 div LValueA;
    except
      Memo1.Lines.Add('except - block');
    end;
  finally
    // 例外が発生してもここは実行される
    FreeAndNil(LStrList);
    Memo1.Lines.Add('finally - block');
  end;

  // ここも実行される
  Memo1.Lines.Add('end procedure');
end;


例外が発生したら処理を中止するには,例えば except ブロックで Exit を実行します.
すると finally ブロックは実行されますから,生成したオブジェクトの解放処理は間違いなく実行されます.しかし,finally ブロックよりも下のコードは実行されません.


図6
except と- finally を使用した例外処理-
  • 例外が発生したら処理を中止する例

リスト6
except と- finally を使用した例外処理.例外が発生したら処理を中止する例
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LValueA  : Integer;
  LValueB  : Integer;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    try
      LValueA := 0;
      LValueB := 50000 div LValueA;
    except
      MessageBox(Handle, 'ゼロで割ることはできません', '情報', MB_ICONINFORMATION);
      Memo1.Lines.Add('except - block');
      // 例外が発生した場合は終了させる
      Exit;
    end;
  finally
    // ここは実行される
    FreeAndNil(LStrList);
    Memo1.Lines.Add('finally - block');
  end;

    // 例外が発生した時は except ブロック内で Exit しているのでここは実行されない
  Memo1.Lines.Add('end procedure');
end;




04_ネストした try - finally - end


try - finally - end がネスト状態になっている場合も,全ての Finally ブロックが実行されます.
その動作確認用のコードです.このサンプルでは,途中の try ブロックで Exit しているため,最後の finally ブロックの後のコードは実行しません.

この場合,途中の finally は本当の意味での finally ではないことになります.ネストしている全ての finally ブロックが実行されることを考えると,実際にはネストする必要がないネストの部分があるかも知れません.ネストが浅い方がコードの可読性は高くなります.
オブジェクトは必要な時に生成し,必要がなくなったら早い段階で解放する,という方針でコーディングする場合,必然的にネストした try - finally - end 構文になることがあります.



図7
ネストした try - finally - end の実行結果

リスト7
ネストした try - finally - end
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    try
      try
        Exit;
      finally
        Memo1.Lines.Add('finally - 3');
      end;
    finally
      Memo1.Lines.Add('finally - 2');
    end;
  finally
    Memo1.Lines.Add('finally - 1');
    FreeAndNil(LStrList);
  end;

  Memo1.Lines.Add('end procedure');
end;