提取嵌套的try / finally块

时间:2011-04-07 13:31:18

标签: delphi refactoring extract try-finally

如何将例程中的嵌套try / finally块“提取”到可重用的实体中?说我有

procedure DoSomething;
var
  Resource1: TSomeKindOfHandleOrReference1;
  Resource2: TSomeKindOfHandleOrReference2;
  Resource3: TSomeKindOfHandleOrReference3;
begin
  AcquireResource1;
  try
    AcquireResource2;
    try
      AcquireResource3;
      try
        // Use the resources
      finally
        ReleaseResource3;
      end;
    finally
      ReleaseResource2;
    end;
  finally
    ReleaseResource1;
  end;
end;

并想要像

这样的东西
TDoSomething = record // or class
strict private
  Resource1: TSomeKindOfHandleOrReference1;
  Resource2: TSomeKindOfHandleOrReference2;
  Resource3: TSomeKindOfHandleOrReference3;
public
  procedure Init; // or constructor
  procedure Done; // or destructor
  procedure UseResources;
end;

procedure DoSomething;
var
  Context: TDoSomething;
begin
  Context.Init;
  try
    Context.UseResources;
  finally
    Context.Done;
  end;
end;

我希望它具有与嵌套原件相同的异常安全性。是否足以在TDoSomething.Init中对资源N变量进行零初始化,并在if Assigned(ResourceN) then中进行一些TDoSomething.Done次检查?

3 个答案:

答案 0 :(得分:5)

关于课程有三件事使这个成语变得安全和容易:

  1. 在构造函数的内存分配阶段(在实际构造函数体运行之前),类引用字段初始化为nil。
  2. 当构造函数中发生异常时,会自动调用析构函数。
  3. 在空引用上调用Free总是安全的,因此您永远不需要先检查Assigned
  4. 由于析构函数可以依赖所有字段来获取已知值,因此无论构造函数在崩溃前获得多远,它都可以安全地调用Free。每个字段都将保留一个有效的对象引用,或者它将为nil,无论哪种方式,都可以安全地释放它。

    constructor TDoSomething.Create;
    begin
      Resource1 := AcquireResource1;
      Resource2 := AcquireResource2;
      Resource3 := AcquireResource3;
    end;
    
    destructor TDoSomething.Destroy;
    begin
      Resource1.Free;
      Resource2.Free;
      Resource3.Free;
    end;
    

    以与使用任何其他类相同的方式使用它:

    Context := TDoSomething.Create;
    try
      Context.UseResources;
    finally
      Context.Free;
    end;
    

答案 1 :(得分:1)

是的,您可以使用单个try / finally / end块来实现零初始化的多个资源。

另一种可能的解决方案可以在Barry Kelly blog

中找到

答案 2 :(得分:1)

最终在Assigned中测试的模式用于Delphi源代码。你做了同样的事情,但我认为你应该移动Context.Init从Context.Init中捕获异常。

procedure DoSomething;
var
  Context: TDoSomething;
begin
  try
    Context.Init;
    Context.UseResources;
  finally
    Context.Done;
  end;
end;

编辑1 如果没有Context.Init和Context.Done,就应该这样做。如果您将所有AquireResource代码放在try之前,如果在AcquireResource2中出现异常,则不会释放Resource1

procedure DoSomething;
var
    Resource1: TSomeKindOfHandleOrReference1;
    Resource2: TSomeKindOfHandleOrReference2;
    Resource3: TSomeKindOfHandleOrReference3;
begin
    Resource1 := nil;
    Resource2 := nil;
    Resource3 := nil;
    try
        AcquireResource1;
        AcquireResource2;
        AcquireResource3;

        //Use the resources

    finally
        if assigned(Resource1) then ReleaseResource1;
        if assigned(Resource2) then ReleaseResource2;
        if assigned(Resource3) then ReleaseResource3;
    end;
end;