Question

I recently stumbled over a problem caused by some very old code I wrote which was obviously assuming that interface references used in a with statement would be released as soon as the with-block is left - kind of like an implicit try-finally-block (similar to C#'s using-statement if I understood correctly).

Apparently (in Delphi 2009) this is not (no longer?) the case. Does anyone know when this happened? Or was my code just plain wrong to begin with?

To clarify, here's a simplified example:

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

Whenever somebody started the old "with is evil" argument this was always the one example I had in mind which kept me going "Yes, but...". Seems like I was wrong... Can anyone confirm?

Was it helpful?

Solution

The with preserved word in Pascal/Delphi is only used for easily accessing the members of records or objects/classes (i.e. in order not to mention the record's/object's/class's name). It's very different from the C# with that relates to garbage collection. It has existed in the Pascal language since the day records were born, to simplify code calling to many data members (back then simply called "fields").

To summarize, with has nothing to do with garbage collection, release of memory or destruction of object instances. Objects that are constructed at the with header could just have been initialized in a separate code line before, it's the same.

OTHER TIPS

This WITH-behavior has never changed. To reach your expected behavior you can change your code in this way:

    procedure TestIt;
    var
       myIntf: IMyIntf; 
    begin
      DoSomething;

      myIntf := TSomeObject.GetMyIntf
      DoStuff;
      DoMoreStuff;
      myIntf := nil; // <- here is where TSomeObject.Destroy called

      DoSomethingElse;
    end; 

or you can do it in procedure:

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; 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top