Today
we got the request to support TInterfacedPersistent in the DI container.
As you may know TInterfacedPersistence does not do reference counting like TInterfacedObject does - or to be more precise it delegates it to its possible owner. Usually the owner will be nil so it does not do reference counting through its _AddRef and _Release method and often is used for classes that should not do reference counting if you don't want to write your own class (or use Spring.TInterfaceBase for that purpose).
When working with DI your life will be much easier when using interfaces. But if the implementing class is of TInterfacedPersistence you might have a problem - in form of a memory leak. The container will create the instance and unless you have registered it as singleton (which means the container will only ever create one instance and return this whenever asking for it) not hold a reference to this instance. Because of the missing reference counting it will never destroyed if the interface reference goes out of scope.
"Then he needs to implement reference counting into that class" my coworker said, when I told him about that request. Good idea but there are a few problems with that. The _AddRef and _Release methods in TInterfacedPersistence are not virtual so you can only "override" them by implementing IInterface again. "Easy enough!" you might say. Yes, but that will only cause reference counting when you query IInterface from the instance but not any other interface that you implemented (and which you more likely will use than IInterface). So you had to re-implement all the other interfaces as well. This will not only add to the instance size (every implemented interface causes the size of each instance to grow by SizeOf(Pointer) - did you know that?) but might not be possible because some implemented methods could be private which would cause the compiler to complain about a missing implementation of the interface.
So, long story short. If you want to make a class that inherits from TInterfacedPersistent reference counted you have to take another approach - we are talking about a class that you cannot modify for whatever reason: provide an owner that does the reference counting and takes care of destroying it. Usually TInterfacedPersistent looks for the owner in its AfterConstruction method. So what we do is look if FOwnerInterface is not already set and then assign an owner to it - but how, it is private? Fortunately there is a trick using a class helper (also could have used RTTI because that has access to private fields by default) - here is the code:
type
TInterfacedPersistentHelper = class helper for TInterfacedPersistent
private type
TOwner = class(TInterfacedObject)
private
FOwnedInstance: TObject;
protected
procedure AfterConstruction; override;
public
constructor Create(const instance: TInterfacedPersistent);
destructor Destroy; override;
end;
public
procedure EnableRefCount;
end;
procedure TInterfacedPersistentHelper.EnableRefCount;
begin
Assert(not Assigned(Self.FOwnerInterface));
Self.FOwnerInterface := TOwner.Create(Self);
end;
constructor TInterfacedPersistentHelper.TOwner.Create(
const instance: TInterfacedPersistent);
begin
inherited Create;
FOwnedInstance := instance;
end;
destructor TInterfacedPersistentHelper.TOwner.Destroy;
begin
FOwnedInstance.Free;
inherited;
end;
procedure TInterfacedPersistentHelper.TOwner.AfterConstruction;
begin
// assigning to FOwnerInterface will increment this to 0
FRefCount := -1;
end;
Now how do we get that into our container? The easiest way would be to use the DelegateTo method in the registration:
container.RegisterType<IFoo, TFoo>.DelegateTo(
function: TFoo
begin
Result := TFoo.Create;
Result.EnsureRefCount;
end);
But what if TFoo would require arguments in its constructor? We would need to Resolve them from the container and inject them. But don't we use the container to get rid of all the construction work?
So with a minor refactoring - isn't it great when things are easy to change without breaking the entire thing - I added support for custom lifetime managers - I assume I don't have to explain what they do. Let's write our own for handling our TInterfacedPersistent classes - we inherit from the TTransientLifetimeManager from Spring.Container.LifetimeManager:
type
TInterfacedPersistentLifetimeManager = class(TTransientLifetimeManager)
protected
procedure DoAfterConstruction(const instance: TValue); override;
end;
procedure TInterfacedPersistentLifetimeManager.DoAfterConstruction(
const instance: TValue);
begin
inherited;
instance.AsType<TInterfacedPersistent>.EnableRefCount;
end;
And now we register our type with it (the API is not yet final the method name might change to something more explicit):
container.RegisterType<IFoo, TFoo>.AsCustom<TInterfacedPersistentLifetimeManager>
Now that is nice already. But what if the container could figure out when a class inherits from TInterfacedPersistent and register this lifetime manager? No problem - we need to add an IBuilderInspector to the container for that purpose. What these do is inspect the registered type for certain aspects (there are built-in ones looking for things like what interfaces does a class implement and register them as services if not already explicitly specified or look for members with special attributes). Ours looks like this:
type
TInterfacedPersistentBuilderInspector = class(TInterfacedObject, IBuilderInspector)
protected
procedure ProcessModel(const kernel: IKernel; const model: TComponentModel);
end;
procedure TInterfacedPersistentBuilderInspector.ProcessModel(const kernel: IKernel;
const model: TComponentModel);
begin
if model.ComponentType.IsInstance
and model.ComponentType.AsInstance.MetaclassType.InheritsFrom(TInterfacedPersistent)
and (model.LifetimeType = TLifetimeType.Transient) then
model.LifetimeManager := TInterfacedPersistentLifetimeManager.Create(model);
end;
And we need to attach that to the container before calling Build (I suggest doing that as the first thing when setting up the container):
container.Kernel.Builder.AddInspector(TInterfacedPersistentBuilderInspector.Create);
But wait, there is more - the container supports so called extensions. These extensions can be attached to the container to add certain behaviors or features (like adding support for decorator detection or changing the way the container selects constructors to create instances). In our case it is really simple as it just adds one component to the container.
type
TInterfacedPersistentLifetimeExtension = class(TContainerExtension)
protected
procedure Initialize; override;
end;
procedure TInterfacedPersistentLifetimeExtension.Initialize;
begin
Kernel.Builder.AddInspector(TInterfacedPersistentBuilderInspector.Create);
end;
And attaching the extension is also a one liner.
container.AddExtension<TInterfacedPersistentLifetimeExtension>;
So in the end with one line we can add support for making TInterfacedPersistent classes reference counted for using them in our DI container powered application.
"I love it when a plan comes together" ;)
P.S. with the same technique you can add reference counting to TComponent, by attaching an IVCLComObject interface to it.