例として、あるテキストファイルの1行目に書かれている文字列を10進数とみなして取り込む、という関数を考えてみます。
type EFileIsEmpty = class(Exception); function ReadIntegerValueFromFile(const Path: String): Integer; var Filename: String; SL: TStringList; begin Filename := IncludeTrailingPathDelimiter(Path) + 'FOO.TXT'; SL := TStringList.Create; try SL.LoadFromFile(Filename); if SL.Count = 0 then begin raise EFileIsEmpty.Create('File is empty.'); end; Result := StrToInt(SL.Strings[0]); finally SL.Free; end; end;
ここで例外が送出される状況には、1.何らかの理由でファイルを開けない(EFOpenError)、2.ファイルが空(0行)だった(EFileIsEmpty)、3.1行目に10進数に変換できない文字があった(EConvertError)の3つがあります(正確にはリソース不足でTStringList.Createが失敗する状況を含め4つですが、今回は考えないことにします)。それぞれの原因に対応した例外クラスがあるため、呼び出し元ではエラーの理由を例外オブジェクトのクラスの違いで知ることができます。
procedure TForm1.Button1Click(Sender: TObject); begin try ReadIntegerValueFromFile('C:\BAR'); except on E: EFOpenError do begin MessageDlg('ファイルを開けませんでした。' + sLineBreak + E.Message, mtInformation,[mbOk],0); Exit; end; on E: EFileIsEmpty do begin MessageDlg('ファイルが空でした。' + sLineBreak + E.Message, mtInformation,[mbOk],0); Exit; end; on E: EConvertError do begin MessageDlg('不正な文字列が入っていました。' + sLineBreak + E.Message, mtInformation,[mbOk],0); Exit; end; end; end;
ここでエラーメッセージにファイル名を表示したい、ということになったとします。ところが呼出元のレベルではファイルの存在するパスはわかっていますがフルパス名はReadIntegerValueFromFileの内部に隠蔽されてしまっています。そこで
type EFileIsEmpty = class(Exception); EFileReadError = class(Exception) private FFilename: String; public constructor Create(const Msg: string; const AFilename: String); property Filename: String read FFilename; end; function ReadIntegerValueFromFile(const Path: String): Integer; var Filename: String; SL: TStringList; begin Filename := IncludeTrailingPathDelimiter(Path) + 'FOO.TXT'; SL := TStringList.Create; try try SL.LoadFromFile(Filename); if SL.Count = 0 then begin raise EFileIsEmpty.Create('File is empty.'); end; Result := StrToInt(SL.Strings[0]); except raise EFileReadError.Create('Error!',Filename); end; finally SL.Free; end; end; constructor EFileReadError.Create(const Msg, AFilename: String); begin inherited Create(Msg); FFilename := AFilename; end;
とすることで
procedure TForm1.Button1Click(Sender: TObject); begin try ReadIntegerValueFromFile('C:\BAR'); except on E: EFileReadError do begin MessageDlg(Format('ファイル ''%s'' の読み込みでエラーが発生しました。' + sLineBreak + '%s', [E.Filename,E.Message]), mtInformation,[mbOk],0); Exit; end; end; end;
のようにエラーがあったときにそのファイルのフルパス名を知ることができます。が、エラーの原因はわからなくなってしまいました。そこで例外チェーンの登場です。クラスプロシージャException.RaiseOuterExceptionを使用することで、その例外ハンドラで受け取った例外オブジェクトを消滅させることなく新たな例外を送出することができます。
type EFileIsEmpty = class(Exception); EFileReadError = class(Exception) private FFilename: String; public constructor Create(const Msg: string; const AFilename: String); property Filename: String read FFilename; end; function ReadIntegerValueFromFile(const Path: String): Integer; var Filename: String; SL: TStringList; begin Filename := IncludeTrailingPathDelimiter(Path) + 'FOO.TXT'; SL := TStringList.Create; try try SL.LoadFromFile(Filename); if SL.Count = 0 then begin raise EFileIsEmpty.Create('File is empty.'); end; Result := StrToInt(SL.Strings[0]); except Exception.RaiseOuterException(EFileReadError.Create('Error!',Filename)); end; finally SL.Free; end; end; constructor EFileReadError.Create(const Msg, AFilename: String); begin inherited Create(Msg); FFilename := AFilename; end;
ここで送出される例外オブジェクトはEFileReadErrorのままです。しかし
procedure TForm1.Button1Click(Sender: TObject); begin try ReadIntegerValueFromFile('C:\BAR'); except on E: EFileReadError do begin if E.InnerException is EFOpenError then begin MessageDlg(Format('ファイル ''%s'' を開けませんでした。' + sLineBreak + '%s', [E.Filename,E.InnerException.Message]), mtInformation,[mbOk],0); end else if E.InnerException is EFileIsEmpty then begin MessageDlg(Format('ファイル ''%s'' が空でした。' + sLineBreak + '%s', [E.Filename,E.InnerException.Message]), mtInformation,[mbOk],0); end else if E.InnerException is EConvertError then begin MessageDlg(Format('ファイル ''%s'' の1行目に不正な文字列が入っていました。' + sLineBreak + '%s', [E.Filename,E.InnerException.Message]), mtInformation,[mbOk],0); Exit; end; Exit; end; end; end;
と例外オブジェクトのInnerExceptionプロパティでException.RaiseOuterExceptionを呼び出した時点での例外オブジェクトを参照することができます。
この例外チェーンに関係するメソッド、プロパティには
- RaiseOuterException: 例外ブロック内で使用し、新しく生成した例外オブジェクトをパラメータとして呼び出すことでその時点での例外オブジェクトをチェーンした例外を送出するExceptionクラスのクラスプロシージャ。
- ThrowOuterException: RaiseOuterExceptionと同じ。C++では例外はraiseするものではなくthrowするものなのでこの名前のクラスプロシージャも用意されている。
- InnerException: 最も近いRaiseOuterExceptionを送出した時点での例外オブジェクトを格納しているプロパティ。通常の例外の送出(raise EXXXX.Create)ではnilとなる。またRaiseOuterExceptionがネストして呼び出されている場合はE.InnerException.InnerException...のようにさかのぼって例外オブジェクトを参照することができる。
- BaseException: ネストしてRaiseOuterExceptionが呼び出された場合に最初に送出された例外オブジェクトを格納しているプロパティ。通常の例外の送出(raise EXXXX.Create)ではnilとなる。また1段階しかRaiseOuterExceptionが呼び出されていない場合はInnerException=BaseExceptionとなる。
- ToString例外チェーン上の全ての例外オブジェクトのMessageプロパティをCR+LFで連結したプロパティ。
元ねたはDELPHI 2009 HANDBOOK。
0 件のコメント:
コメントを投稿