例として、あるテキストファイルの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 件のコメント:
コメントを投稿