【问题标题】:Delphi: Since when are interface references no longer released at the end of a with-block?Delphi:从什么时候开始不再在 with 块的末尾释放接口引用?
【发布时间】:2010-11-17 01:17:30
【问题描述】:

我最近偶然发现了一个由我编写的一些非常旧的代码引起的问题,该代码显然假设with 语句中使用的接口引用将在with-block 离开后立即释放 - 有点像隐式try-finally-block(如果我理解正确,类似于C#的using-statement)。

显然(在 Delphi 2009 中)这不是(不再是?)情况。有谁知道这是什么时候发生的?还是我的代码一开始就完全错误?

为了澄清,这里有一个简化的例子:

type
  IMyIntf = interface;
  TSomeObject = class(TInterfacedObject, IMyIntf)
  protected
    constructor Create; override; // creates some sort of context
    destructor Destroy; override; // cleans up the context created in Create
  public
    class function GetMyIntf: IMyIntf; //a factory method, calling the constructor
  end;

procedure TestIt;
begin
  DoSomething;
  with (TSomeObject.GetMyIntf) do
    begin
      DoStuff;
      DoMoreStuff;
    end; // <- expected: TSomeObject gets destroyed because its ref.count is decreased to 0
  DoSomethingElse;
end; // <- this is where TSomeObject.Destroy actually gets called

每当有人开始旧的“with 是邪恶的”论点时,这始终是我想到的一个例子,它让我继续“是的,但是......”。好像我错了……谁能确认一下?

【问题讨论】:

    标签: delphi interface delphi-2009 reference-counting with-statement


    【解决方案1】:

    Pascal/Delphi 中的with 保留字仅用于轻松访问记录或对象/类的成员(即,为了不提及记录/对象/类的名称)。它与与垃圾回收相关的 C# with 非常不同。自从records 诞生之日起,它就存在于 Pascal 语言中,以简化对许多数据成员的代码调用(当时简称为“字段”)。

    总而言之,with 与垃圾收集、内存释放或对象实例销毁无关。在 with 标头处构造的对象之前可能只是在单独的代码行中初始化,它是相同的。

    【讨论】:

    • 正确,创建的对象的范围是它的所有者,在这种情况下是过程。在上面的例子中,它应该在最后被销毁。任何其他操作都是出乎意料的,并且可能是在 D2009 中更正的错误。
    • 自从在版本 3 中将接口引入 Delphi 以来,行为一直是相同的。没有更正的错误。奥利弗只是记错了事情是如何运作的。又一个“with”导致意外结果的例子。
    • 我一直想象with 会创建一个匿名的、范围狭窄的局部变量,它只存在于with 块中。即使您无法再访问它,内存管理器仍然保留该引用对我来说似乎毫无意义...鉴于(现在看来:可悲的是不正确的)世界观,我认为期望一旦引用超出范围,引用计数就会减少...
    • 澄清一下:我从没想过with-block 会直接影响被引用对象的生命周期——我只希望隐含局部变量的范围仅限于该块,因此它一旦该块结束,引用计数就会减少。
    【解决方案2】:

    这种 WITH 行为从未改变。为了达到您的预期行为,您可以通过以下方式更改您的代码:

        procedure TestIt;
        var
           myIntf: IMyIntf; 
        begin
          DoSomething;
    
          myIntf := TSomeObject.GetMyIntf
          DoStuff;
          DoMoreStuff;
          myIntf := nil; // <- here is where TSomeObject.Destroy called
    
          DoSomethingElse;
        end; 
    

    或者你可以在程序中这样做:

    procedure TestIt;
    
       procedure DoAllStuff;
       var
          myIntf: IMyIntf; 
       begin
          myIntf := TSomeObject.GetMyIntf
          DoStuff;
          DoMoreStuff;
       end;  // <- here is where TSomeObject.Destroy called
    
    begin    
      DoSomething;
      DoAllStuff;
      DoSomethingElse;
    end; 
    

    【讨论】:

    • 不幸的是,这两种选择都违背了保持代码简洁的主要意图之一......此外,在第一个示例中,为了清楚起见,我将添加一个 try-finally 块(尽管不是绝对必要的)我也不喜欢声明仅用于滥用的“空”接口类型的变量^D^D^D^D^D利用接口对象的引用计数性质......我喜欢尽可能保持匿名......但这是不可否认的只是我的个人特质
    猜你喜欢
    • 2016-01-03
    • 2015-06-25
    • 2015-06-26
    • 2012-06-12
    • 2019-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多