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.
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.