Delphi异常处理-如何正确清理?

Delphi异常处理-如何正确清理?,delphi,exception-handling,Delphi,Exception Handling,我正在查看我们的应用程序中的一些代码,发现一些与我通常所做的有点奇怪的事情。对于异常处理和清理,我们(以及许多其他程序员,我确信)使用嵌入了Try/Except块的Try/Finally块。现在我习惯了Try/Except在Try/Finally中这样: Try Try CouldCauseError(X); Except HandleError; end; Finally FreeAndNil(x); end; 但另一个代码块则相反,如下所示: Try T

我正在查看我们的应用程序中的一些代码,发现一些与我通常所做的有点奇怪的事情。对于异常处理和清理,我们(以及许多其他程序员,我确信)使用嵌入了Try/Except块的Try/Finally块。现在我习惯了Try/Except在Try/Finally中这样:

Try
  Try
    CouldCauseError(X);
  Except
    HandleError;
  end;
Finally
  FreeAndNil(x);
end;
但另一个代码块则相反,如下所示:

Try
  Try
    CouldCauseError(X);
  Finally
    FreeAndNil(x);
  end;
Except
  HandleError;
end;

环顾网络,我看到人们都这样做,没有解释为什么。我的问题是,哪一个获得外部块和哪一个获得内部块重要吗?或者,无论采用哪种结构,Exception和finally部分都会得到处理吗?谢谢。

最终和例外都将触发,顺序由您决定。这取决于你想在你的最后或除了块做什么。是否要释放块中使用的内容?将finally放在except块周围。

一个区别是try..finally..except可能容易受到异常屏蔽情况的影响

假设在CouldCauseError()中发生异常。然后设想在finally中尝试FreeAndNIL(X)会导致另一个异常。原始异常(很可能导致不稳定导致FreeAndNIL()异常)丢失。except处理程序现在正在处理在原始异常之后发生的“下游”异常

尝试..除了..最后当然避免了这一点,因此应优先考虑(处理尽可能接近其来源的异常)

处理此类简单情况(正在清理的单个对象)的另一种方法是在正常流和异常处理程序中包含清理:

try
  CouldCauseError(X);
  FreeAndNil(x);
except
  HandleError;
  FreeAndNil(x);
end;
一开始这看起来有点吓人(“我需要确保调用了FreeAndNIL(X),所以我必须有一个FINALLY”),但是第一个FreeAndNIL()可能不会被调用的唯一方法是,如果有异常,并且如果有异常,您也可以使用FreeAndNIL(),它还使异常情况下的清理顺序更加清晰(从某种意义上说,为了理解发生了什么,在某种程度上必须“过滤”掉噪声)

但是,我个人不喜欢它-如果您更改异常处理程序或正常流中的代码,您可能会破坏清理行为,但是根据这样一个块周围的代码以及块本身的大小,在某些情况下,为了简化,可以认为“噪波”的减少是合理的

然而,这取决于这样一个事实,free和nil()实际上是“NILThenFree()”X在释放之前为零,因此,如果正常流中的FreeAndNIL(X)中发生异常,那么当异常处理程序捕捉到由X.Free引发的异常时,X将为零,因此它不会尝试“双重释放”X


无论您做出什么决定,我都希望这会有所帮助。

这取决于finally块中的代码是否能够引发异常本身(然后它需要受到上层try的保护,但除外),或者您在异常处理中是否需要稍后释放的内容(然后需要在较高级别的finally块中释放它)

所有这些都意味着,有时您甚至可以编写一些代码,如:

  try
    try
      try
        CouldCauseError(X);
      except
        HandleErrorWith(X);
      end;
    finally
      FreeAndNil(X); // and/or any resource cleanup
    end;
  except
    CatchAllError;
  end;

一开始你的代码看起来有点奇怪。我怀念X的创建

X := CreateAnX
try
  DoSomeThing(X);
finally
  FreeAndNil(x);
end;
这很重要,因为如果你有这样的代码

try
  X := CreateAnX
  try
    DoSomeThing(X);
  finally
    FreeAndNil(x);
  end;
except
  on EIdException do
    raise;
  on e: Exception do
    LogException(e)
end;      
但是,当你想要捕获所有错误时,一定要考虑它。作为一个例子(我经常认为它是错误的),不要在Indy OneExecute处理程序中这样做。在这里,你必须使用类似这样的东西

try
  X := CreateAnX
  try
    DoSomeThing(X);
  finally
    FreeAndNil(x);
  end;
except
  on EIdException do
    raise;
  on e: Exception do
    LogException(e)
end;      
如果由于抛出异常或(例如)对话可能失败而预期出现异常,请查找最内部的位置以捕获错误:

X := CreateAnX
try
  DoSomeThing(X);
  try
    i := StrToInt(X.Text);
  except
    on EConvertError do
      i := 0;
  end;       
finally
  FreeAndNil(x);
end;
不要这样做

X := CreateAnX
try
  try
    DoSomeThing(X);
    i := StrToInt(X.Text);
  except
    on EConvertError do
      i := 0;
  end;       
finally
  FreeAndNil(x);
end;
只有在DoSomeThing中也会出现EConvertError时才执行此操作。如果DoSomeThing引发EConvertError而您没有预料到,则代码存在需要更正的严重问题。在这种情况下,请确保用户可以保存其工作(可能作为副本,因为他的工作可能会被损坏)并确保您获得有关问题的信息


至少try except是为了处理异常,而不是为了隐藏异常。

删除crete语句是故意的,因为我认为它与begin和end语句一样,与问题无关。是的,我们总是在第一个try语句之前创建X。
X := CreateAnX
try
  DoSomeThing(X);
  try
    i := StrToInt(X.Text);
  except
    on EConvertError do
      i := 0;
  end;       
finally
  FreeAndNil(x);
end;
X := CreateAnX
try
  try
    DoSomeThing(X);
    i := StrToInt(X.Text);
  except
    on EConvertError do
      i := 0;
  end;       
finally
  FreeAndNil(x);
end;