views:

266

answers:

2

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?

+14  A: 

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.

Roee Adler
Correct, the scope of an object created is it's owner, the procedure in this case. In the above example, it should be destroyed at the last end. Any other action would be unexpected, and probably a bug that was corrected in D2009.
dverespey
The behavior has been the same since interfaces were introduced to Delphi, in version 3. There is no bug that was corrected. Oliver has simply mis-remembered how things work. Yet another example of "with" leading to unexpected results.
Rob Kennedy
I always imagined `with` to create an anonymous, tightly-scoped local variable that only existed inside the `with`-block. It seemed pointless to me for the memory manager to hang on to that reference even when you could no longer access it...Given that (as it now seems: sadly incorrect) view of the world I think it was perfectly valid to expect the reference count to be decremented as soon as that reference went out of scope...
Oliver Giesen
And to clarify: I never expected the `with`-block to directly affect the lifetime of the referred object - I only expected the scope of the implied local variable to be limited to that block and thus its reference count to be decremented as soon as that block ended.
Oliver Giesen
+2  A: 

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;
Heinz Z.
unfortunately both alternatives defy one of the main intentions of keeping the code terse... also, in the first example I would add a `try-finally` block for clarity (though not strictly necessary)I also dislike declaring variables of "empty" interface types that exist solely to abuse^D^D^D^D^Dtake benefit of the reference-counted nature of interfaced objects... I like to keep those anonymous where possible... but that's admittedly just a personal idiosyncrasy of mine
Oliver Giesen