执行嵌套 TRY / FINALLY 语句的最佳实践

发布于 2024-07-11 12:21:17 字数 836 浏览 4 评论 0原文

嗨,进行嵌套尝试的最佳方法是什么? 最后在delphi中声明?

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := TClientDataSet.Create(application );
  try
    cds2      := TClientDataSet.Create(application );
    try
      cds3      := TClientDataSet.Create(application );
      try
        cds4      := TClientDataSet.Create(application );
        try
        ///////////////////////////////////////////////////////////////////////
        ///      DO WHAT NEEDS TO BE DONE
        ///////////////////////////////////////////////////////////////////////
        finally
          cds4.free;
        end;

      finally
        cds3.free;
      end;
    finally
      cds2.free;
    end;
  finally
    cds1.free;
  end;
end;

你能建议一个更好的方法吗?

Hi What is the best way to do nested try & finally statements in delphi?

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := TClientDataSet.Create(application );
  try
    cds2      := TClientDataSet.Create(application );
    try
      cds3      := TClientDataSet.Create(application );
      try
        cds4      := TClientDataSet.Create(application );
        try
        ///////////////////////////////////////////////////////////////////////
        ///      DO WHAT NEEDS TO BE DONE
        ///////////////////////////////////////////////////////////////////////
        finally
          cds4.free;
        end;

      finally
        cds3.free;
      end;
    finally
      cds2.free;
    end;
  finally
    cds1.free;
  end;
end;

Can you Suggest a better way of doing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

眉黛浅 2024-07-18 12:21:17

下面怎么样:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil;
  cds3      := Nil;
  cds4      := Nil;
  try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);
    cds3      := TClientDataSet.Create(nil);
    cds4      := TClientDataSet.Create(nil);
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds4);
    freeandnil(cds3);
    freeandnil(cds2);
    freeandnil(Cds1);
  end;
end;

这使其保持紧凑,并且仅尝试释放已创建的实例。 实际上没有必要执行嵌套,因为任何失败都会导致放弃到最后并执行您提供的示例中的所有清理。

就我个人而言,我尽量不要嵌套在同一个方法中......但 try/try/ except/finally 场景除外。 如果我发现自己需要嵌套,那么对我来说,这是考虑重构到另一个方法调用的好时机。

编辑由于mghieutku

编辑将对象创建更改为不引用应用程序,因为在本示例中没有必要。

how about the following:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil;
  cds3      := Nil;
  cds4      := Nil;
  try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);
    cds3      := TClientDataSet.Create(nil);
    cds4      := TClientDataSet.Create(nil);
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds4);
    freeandnil(cds3);
    freeandnil(cds2);
    freeandnil(Cds1);
  end;
end;

This keeps it compact, and only attempts to free the instances which were created. There really is no need to perform the nesting since ANY failure will result in dropping to the finally and performing all of the cleanup in the example you provided.

Personally I try not to nest within the same method... with the exception being a try/try/except/finally scenario. If I find myself needing to nest, then to me that is a great time to think refactoring into another method call.

EDIT Cleaned up a bit thanks to the comments by mghie and utku.

EDIT changed the object creation to not reference application, as its not necessary in this example.

狂之美人 2024-07-18 12:21:17

我会使用这样的东西:

var
  Safe: IObjectSafe;
  cds1 : TClientDataSet;
  cds2 : TClientDataSet;
  cds3 : TClientDataSet;
  cds4 : TClientDataSet;
begin
  Safe := ObjectSafe;
  cds1 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds2 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds3 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds4 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  ///////////////////////////////////////////////////////////////////////
  ///      DO WHAT NEEDS TO BE DONE
  ///////////////////////////////////////////////////////////////////////

  // if Safe goes out of scope it will be freed and in turn free all guarded objects
end;

有关接口的实现,请参阅这篇文章,但您可以轻松地自己创建类似的东西。

编辑:

我刚刚注意到链接文章中的 Guard() 是一个过程。 在我自己的代码中,我重载了返回 TObject 的 Guard() 函数,上面的示例代码假设了类似的情况。 当然,使用泛型现在可以编写更好的代码...

编辑2:

如果您想知道为什么 try ...finally 在我的代码中被完全删除:在不引入可能性的情况下删除嵌套块是不可能的内存泄漏(当析构函数引发异常时)或访问冲突。 因此,最好使用辅助类,并让接口的引用计数完全接管。 即使某些析构函数引发异常,辅助类也可以释放它所保护的所有对象。

I'd use something like this:

var
  Safe: IObjectSafe;
  cds1 : TClientDataSet;
  cds2 : TClientDataSet;
  cds3 : TClientDataSet;
  cds4 : TClientDataSet;
begin
  Safe := ObjectSafe;
  cds1 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds2 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds3 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds4 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  ///////////////////////////////////////////////////////////////////////
  ///      DO WHAT NEEDS TO BE DONE
  ///////////////////////////////////////////////////////////////////////

  // if Safe goes out of scope it will be freed and in turn free all guarded objects
end;

For the implementation of the interface see this article, but you can easily create something similar yourself.

EDIT:

I just noticed that in the linked article Guard() is a procedure. In my own code I have overloaded Guard() functions that return TObject, above sample code assumes something similar. Of course with generics much better code is now possible...

EDIT 2:

If you wonder why try ... finally is completely removed in my code: It's impossible to remove the nested blocks without introducing the possibility of memory leaks (when destructors raise exceptions) or access violations. Therefore it's best to use a helper class, and let the reference counting of interfaces take over completely. The helper class can free all objects it guards, even if some of the destructors raise exceptions.

戏舞 2024-07-18 12:21:17

没有嵌套尝试的代码还有另一种变体...最后我才想到。 如果您不将构造函数的 AOwner 参数设置为 nil 来创建组件,那么您可以简单地利用 VCL 免费提供的生命周期管理:

var
  cds1: TClientDataSet;
  cds2: TClientDataSet;
  cds3: TClientDataSet;
  cds4: TClientDataSet;
begin
  cds1 := TClientDataSet.Create(nil);
  try
    // let cds1 own the other components so they need not be freed manually
    cds2 := TClientDataSet.Create(cds1);
    cds3 := TClientDataSet.Create(cds1);
    cds4 := TClientDataSet.Create(cds1);

    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////

  finally
    cds1.Free;
  end;
end;

我是小代码的忠实拥护者(如果不是)太混乱了)。

There's another variation of the code without nested try ... finally that just occurred to me. If you don't create the components with the AOwner parameter of the constructor set to nil, then you can simply make use of the lifetime management that the VCL gives you for free:

var
  cds1: TClientDataSet;
  cds2: TClientDataSet;
  cds3: TClientDataSet;
  cds4: TClientDataSet;
begin
  cds1 := TClientDataSet.Create(nil);
  try
    // let cds1 own the other components so they need not be freed manually
    cds2 := TClientDataSet.Create(cds1);
    cds3 := TClientDataSet.Create(cds1);
    cds4 := TClientDataSet.Create(cds1);

    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////

  finally
    cds1.Free;
  end;
end;

I'm a big believer in small code (if it's not too obfuscated).

雨的味道风的声音 2024-07-18 12:21:17

如果你想走这条(IMO)丑陋的路线(将初始化设置为 nil 的组处理以了解是否需要释放),你至少必须保证你不会让析构函数之一中的异常阻止释放其余的你的对象。
就像是:

function SafeFreeAndNil(AnObject: TObject): Boolean;
begin
  try
    FreeAndNil(AnObject);
    Result :=  True;
  except
    Result := False;
  end;
end;

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    IsOK1 : Boolean;
    IsOK2 : Boolean;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    IsOk2 := SafeFreeAndNil(cds2);    // an error in freeing cds2 won't stop execution
    IsOK1 := SafeFreeAndNil(Cds1);
    if not(IsOk1 and IsOk2) then
      raise EWhatever....
  end;
end;

If you want to go this (IMO) ugly route (group handling with initialization to nil to know if freeing is needed), you at least MUST guarantee that you won't let an exception in one of the destructor prevent from freeing the rest of your objects.
Something like:

function SafeFreeAndNil(AnObject: TObject): Boolean;
begin
  try
    FreeAndNil(AnObject);
    Result :=  True;
  except
    Result := False;
  end;
end;

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    IsOK1 : Boolean;
    IsOK2 : Boolean;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    IsOk2 := SafeFreeAndNil(cds2);    // an error in freeing cds2 won't stop execution
    IsOK1 := SafeFreeAndNil(Cds1);
    if not(IsOk1 and IsOk2) then
      raise EWhatever....
  end;
end;
埋葬我深情 2024-07-18 12:21:17

有一个关于构造函数中的异常的精彩视频 析构函数

它显示了一些很好的示例,例如:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds2);    //// what has if there in an error in the destructor of cds2
    freeandnil(Cds1);
  end;
end;

如果 cds2 的析构函数中出现错误,会发生什么

Cds1 将不会被销毁

编辑

另一个很好的资源是:

Jim McKeeth 关于 代码范围 III 中的延迟异常处理,他谈到了在 finally 块中处理异常的问题。

There is a good video on exceptions in constructors & destructors

It shows some nice examples such as:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds2);    //// what has if there in an error in the destructor of cds2
    freeandnil(Cds1);
  end;
end;

What has if there in an error in the destructor of cds2

Cds1 will not be Destroyed

EDIT

Another good resource is :

Jim McKeeth excellent video on Delayed Exception Handling in code range III were he talks about problems in handling exceptions in the finally block.

呆橘 2024-07-18 12:21:17

@mghie:Delphi 有堆栈分配的对象:

type
  TMyObject = object
  private
    FSomeField: PInteger;
  public
    constructor Init;
    destructor Done; override;
  end;

constructor TMyObject.Init;
begin
  inherited Init;
  New(FSomeField);
end;

destructor TMyObject.Done;
begin
  Dispose(FSomeField);
  inherited Done;
end;

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  /// ...
end;

不幸的是,如上面的示例所示:堆栈分配的对象不能防止内存泄漏。

因此,这仍然需要像这样调用析构函数:

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  try
    /// ...
  finally
    MyObject.Done;
  end;
end;

好吧,我承认,这几乎是偏离主题的,但我认为在这种情况下可能会很有趣,因为提到了堆栈分配对象作为解决方案(它们不是如果没有自动析构函数调用)。

@mghie: Delphi has got stack allocated objects:

type
  TMyObject = object
  private
    FSomeField: PInteger;
  public
    constructor Init;
    destructor Done; override;
  end;

constructor TMyObject.Init;
begin
  inherited Init;
  New(FSomeField);
end;

destructor TMyObject.Done;
begin
  Dispose(FSomeField);
  inherited Done;
end;

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  /// ...
end;

Unfortunately, as the above example shows: Stack allocated objects do not prevent memory leaks.

So this would still require a call to the destructor like this:

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  try
    /// ...
  finally
    MyObject.Done;
  end;
end;

OK, I admit it, this is very nearly off topic, but I thought it might be interesting in this context since stack allocated objects were mentioned as a solution (which they are not if there is no automatic destructor call).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文