Delphi 提取嵌套的try/finally块

Delphi 提取嵌套的try/finally块,delphi,refactoring,extract,try-finally,Delphi,Refactoring,Extract,Try Finally,如何将嵌套的try/finally块从例程“提取”到可重用实体中?说我有 procedure DoSomething; var Resource1: TSomeKindOfHandleOrReference1; Resource2: TSomeKindOfHandleOrReference2; Resource3: TSomeKindOfHandleOrReference3; begin AcquireResource1; try AcquireResource2;

如何将嵌套的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;

我希望它与嵌套的原始文件具有相同的异常安全性。在
tdosome.Init
中对ResourceN变量进行零初始化并执行一些
操作是否足够?如果已分配(ResourceN),则
检查
tdosome.Done

是的,您可以使用一个try/finally/end块对多个资源进行零初始化


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

中找到,在Delphi源代码中使用了在finally中指定测试的模式。您可以做类似的事情,但我认为您应该移动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的情况下,您应该这样做。如果您将所有AcquireResource代码放在
try
之前,则如果AcquireResource2中出现异常,您将无法释放资源1

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;

关于类,有三件事可以让这个习惯用法变得安全和简单:

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

    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;
    

    @失望先生:如果这能减轻你的痛苦,想象一下三个嵌套块被提取成三个例程,然后以嵌套方式调用它们。:-)这并没有改变问题的核心。嘿-那评论到哪里去了?:-)是的-我在结束“天哪!”的时候删除了这个评论我不是一个德尔福的人,突然想起了更糟糕的事情,所以我很快就想起了这一刻但是,我想问:为什么你不能简单地使用一个
    try/finally
    ,确定哪些资源没有被获取,然后处理那些已经被获取的资源?我想这就是你的方法的方向。是的。我这么问是因为我不想不经意间把异常安全抛出窗外。谢谢你的想法。我脑海中已经出现了某种防护界面,但对我来说,这似乎总是太过分了。顺便说一句:我对巴里的帖子发表了评论。:-)嗯,将Init放在try块中似乎是错误的。你这么说是因为我把它做成了唱片吗。如果它是一个类,你会写
    try Context:=TDoSomething.Create?它似乎是错误的,因为它是错误的。如果初始化引发异常,则您不想完成任何操作,因为您不知道什么是可以安全完成的。@Mikael:这可能与中的问题相同。我记得有很多关于非常微妙的问题的讨论@乌尔里希-是的,被接受的答案和我建议的一样。这是否意味着您的问题应标记为重复:)?不,Mikael,这里接受的答案与您在此处显示的答案不同。如果
    收单机构资源2
    引发异常,
    Resource2
    Resource3
    尚未初始化,因此使用
    分配的
    检查其当前值是错误的。在输入
    try
    块之前,您需要初始化内容。我无法利用第3项,因为ReleaseResource不一定是对TObject.Free的调用。但是如果分配了
    ,我可以用
    解决这个问题,而且项目2似乎是最重要的一点。因此,我将继续使用惯用方法并从中获益。我通常将构造函数调用放在try..finally块中。我刚刚意识到(根据你的观点2),这是没有必要的。不过,我不确定我是否会改变我的习惯,因为我发现这一点更加清楚。你怎么看?按照
    TObject.Free
    FreeMem
    的例子,让
    ReleaseResource
    安全地调用空资源。这让其他地方的事情变得容易多了。这不仅没有必要,@PA,这是错误的。如果构造函数抛出,那么将结果赋给的变量未初始化,因此您正在对未初始化的值调用
    Free