15

I would expect that the reference counting should work on the outer aggregating object in an interface implementation. If I can refer to another example: Clarity in classes implementing multiple interfaces (alternative to delegation):

Here is a minimal reproduction of the behaviour:

program SO16210993;

{$APPTYPE CONSOLE}

type
  IFoo = interface
    procedure Foo;
  end;

  TFooImpl = class(TInterfacedObject, IFoo)
    procedure Foo;
  end;

  TContainer = class(TInterfacedObject, IFoo)
  private
    FFoo: IFoo;
  public
    constructor Create;
    destructor Destroy; override;
    property Foo: IFoo read FFoo implements IFoo;
  end;

procedure TFooImpl.Foo;
begin
  Writeln('TFooImpl.Foo called');
end;

constructor TContainer.Create;
begin
  inherited;
  FFoo := TFooImpl.Create;
end;

destructor TContainer.Destroy;
begin
  Writeln('TContainer.Destroy called');//this line never runs
  inherited;
end;

procedure Main;
var
  Foo : IFoo;
begin
  Foo := TContainer.Create;
  Foo.Foo;
end;

begin
  Main;
  Readln;
end.

If instead of using implements, I implement the interface in the TImplementor class then the destructor runs.

Community
  • 1
  • 1
Gryffe
  • 413
  • 1
  • 3
  • 9
  • 5
    "Am I missing something?" I don't know. But we certainly are. You forgot to include the code! Full program that demonstrates behaviour is required. Otherwise we have to guess. – David Heffernan Apr 25 '13 at 09:24
  • 1
    you have some extra references or reference loops. Add overrides for TFirstSecond._AddRef and TFirstSecond._Release and put breakpoints there, get a full list of references and see which ones were not cleared – Arioch 'The Apr 25 '13 at 10:43
  • Well, the issue is that your interfaces are delegated. Not sure why that causes this behaviour. – David Heffernan Apr 25 '13 at 13:37
  • Good question. I took the liberty of producing a really simple demonstration test case. – David Heffernan Apr 25 '13 at 13:40
  • The idea of delegation is a nice official feature in Delphi, but it appears not to work? – Gryffe Apr 25 '13 at 13:42

1 Answers1

16

What is happening here is that you call TContainer.Create and create an instance to an object. But you then assign that instance to an interface reference, the global variable Foo. Because that variable is of type IFoo, the interface delegation means that the implementing object is the instance of TFooImpl and not the instance of TContainer.

Hence nothing ever takes a reference to the instance of TContainer, its reference count is never increased, and so it is never destroyed.

I don't think there's a very easy way around this. You may be able to use TAggregatedObject but it may not solve your problem. It would force you to declare TContainer.FFoo to be of type TFooImpl which I imagine you do not want to do. Anyhow, here's what it looks like re-cast that way:

program SO16210993_TAggregatedObject;

{$APPTYPE CONSOLE}

type
  IFoo = interface
    procedure Foo;
  end;

  TFooImpl = class(TAggregatedObject, IFoo)
    procedure Foo;
  end;

  TContainer = class(TInterfacedObject, IFoo)
  private
    FFoo: TFooImpl;
    function GetFoo: IFoo;
  public
    destructor Destroy; override;
    property Foo: IFoo read GetFoo implements IFoo;
  end;

procedure TFooImpl.Foo;
begin
  Writeln('TFooImpl.Foo called');
end;

destructor TContainer.Destroy;
begin
  Writeln('TContainer.Destroy called');//this line does run
  FFoo.Free;
  inherited;
end;

function TContainer.GetFoo: IFoo;
begin
  if not Assigned(FFoo) then
    FFoo := TFooImpl.Create(Self);
  Result := FFoo;
end;

procedure Main;
var
  Foo : IFoo;
begin
  Foo := TContainer.Create;
  Foo.Foo;
end;

begin
  Main;
  Readln;
end.

The documentation does talk about this:

The class you use to implement the delegated interface should derive from TAggregationObject.

Initially I could not find any documentation for this TAggregationObject. And finally I realised that it's actually named TAggregatedObject and is documented.

TAggregatedObject provides the functionality for an inner object of an aggregate by implementing the IInterface methods to delegate to the controlling IInterface.

An aggregated object is an object composed of several interfaced objects. Each object implements its own behavior and interfaces, but all the objects share the same reference count, which is that of the controller object. In the container pattern, the controller is the container object.

TAggregatedObject does not itself support any interfaces. However, as is typical of an aggregate, it does implement the methods of IInterface, which are used by the objects that descend from it. TAggregatedObject, therefore, serves as a base for classes that implement interfaces for creating objects that are part of an aggregate.

TAggregatedObject is used as a base for classes that create contained objects and connecting objects. Using TAggregatedObject ensures that calls to the IInterface methods delegate to the controlling IInterface of the aggregate.

The controlling IInterface is specified in the constructor for TAggregatedObject and is indicated by the Controller property.

In addition there is this from the source code comments:

TAggregatedObject and TContainedObject are suitable base classes for interfaced objects intended to be aggregated or contained in an outer controlling object. When using the "implements" syntax on an interface property in an outer object class declaration, use these types to implement the inner object.

Interfaces implemented by aggregated objects on behalf of the controller should not be distinguishable from other interfaces provided by the controller. Aggregated objects must not maintain their own reference count - they must have the same lifetime as their controller. To achieve this, aggregated objects reflect the reference count methods to the controller.

TAggregatedObject simply reflects QueryInterface calls to its controller. From such an aggregated object, one can obtain any interface that the controller supports, and only interfaces that the controller supports. This is useful for implementing a controller class that uses one or more internal objects to implement the interfaces declared on the controller class. Aggregation promotes implementation sharing across the object hierarchy.

TAggregatedObject is what most aggregate objects should inherit from, especially when used in conjunction with the "implements" syntax.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @FabricioAraujo I found the docs in the end. There was a typo!! – David Heffernan Apr 25 '13 at 20:07
  • I was mislead by the countless examples I found elsewhere. It makes perfect sense that the reference count has to take place somewhere. Thanks for answering – Gryffe Apr 26 '13 at 07:32
  • You are welcome. It was an interesting learning experience for me! – David Heffernan Apr 26 '13 at 07:34
  • Marcos Santos came up with what it seems to me an elegant solution to allow an interface implementation to be used both as a regular object and as an aggregated object [link](http://www.microsofttranslator.com/bv.aspx?from=&to=en&a=http%3A%2F%2Fobjectpascalprogramming.com%2Fposts%2Fobjetos-agregados%2F). The solution is descending the implementation class from TInterfacedObject, so it can be used as a standalone object and a decorator descending from TAggregatedObject whenever the same implementation is to be used with the keyword implements. – nunopicado Dec 12 '16 at 14:05