MultiCast Events: auto-remove-on-free for non-components?

Issue #51 wontfix
Arioch The created an issue

When a listener object, that registered his method in the event, is destroyed, it is neat if the de-registration was done automatically.

This is implemented now when the listener object is derived from TComponent.

However sometimes TComponent is not wanted for being too heavy or for any other reason.

It would be nice if Spring.Event offered another method for non-component auto-deregistration. Of course it would still require some plumbing, but the boiler plate can be made very small.

See the neat implementation of pluggable auto-remove at

Comments (3)

  1. Stefan Glienke repo owner

    The interface is no option because this might cause object destruction in certain situations. Also any option will always cause some additional overhead no matter how it will be implemented.

    But thanks to the flexible design of the events you can write your own one that does this - here is an example (I did not test it through completely):

    type
      TObserverEvent<T> = class(TEvent<T>)
      private
        fHash: TDictionary<Pointer,Integer>;
        function GetOnDestroyEvent(obj: TObject): IEvent<TNotifyEvent>;
        procedure HandleDestroy(Sender: TObject);
      protected
        procedure Notify(Sender: TObject; const Item: TMethod;
          Action: TCollectionNotification); override;
      public
        constructor Create;
        destructor Destroy; override;
      end;
    
    constructor TObserverEvent<T>.Create;
    begin
      inherited;
      fHash := TDictionary<Pointer,Integer>.Create;
    end;
    
    destructor TObserverEvent<T>.Destroy;
    begin
      inherited;
      fHash.Free;
    end;
    
    function TObserverEvent<T>.GetOnDestroyEvent(
      obj: TObject): IEvent<TNotifyEvent>;
    var
      context: TRttiContext;
      rttiType: TRttiType;
      prop: TRttiProperty;
      value: TValue;
    begin
      rttiType := context.GetType(obj.ClassType);
      if Assigned(rttiType) then
      begin
        prop := rttiType.GetProperty('OnDestroy');
        if Assigned(prop) then
        begin
          value := prop.GetValue(obj);
          if not value.IsEmpty and value.IsType<IEvent<TNotifyEvent>> then
            Result := value.AsType<IEvent<TNotifyEvent>>;
        end;
      end;
    end;
    
    procedure TObserverEvent<T>.HandleDestroy(Sender: TObject);
    begin
      RemoveAll(Sender);
    end;
    
    procedure TObserverEvent<T>.Notify(Sender: TObject; const Item: TMethod;
      Action: TCollectionNotification);
    var
      event: IEvent<TNotifyEvent>;
      refCount: Integer;
    begin
      inherited;
      case Action of
        cnAdded:
        begin
          if not fHash.TryGetValue(Item.Data, refCount) then
          begin
            event := GetOnDestroyEvent(Item.Data);
            if Assigned(event) then
            begin
              event.Add(HandleDestroy);
              fHash.Add(Item.Data, 1);
            end;
          end
          else
            fHash[Item.Data] := refCount + 1;
        end;
        cnRemoved:
        begin
          if fHash.TryGetValue(Item.Data, refCount) then
          begin
            if refCount = 1 then
            begin
              event := GetOnDestroyEvent(Item.Data);
              event.Remove(HandleDestroy);
              fHash.Remove(Item.Data);
            end
            else
              fHash[Item.Data] := refCount - 1;
          end;
        end;
      end;
    end;
    
  2. Arioch The reporter

    Thanks. I would review it when have more time. It is not a trivial matter.

    Preliminary reply would be two points.

    1) sure, any framework might be abused. Especially one, trying to bring safe language features into a cpu-native language. Misusing this event can "cause AV in smoe situations", which however does not mean it is too dangerous to be used. It is aways a tradeoff.

    2) The linked sequence of articles and the code to mee seem sophisticated enough to make sudden destruction nearly impossible given proper use of the solution linked. Yes, misuse IS possible, yet Jolyon's scaffolding seems ot make "safe" use straightforward and easy.

    3) with your implementation i worry about inherited call in .Notify - it looks like if we would have a TComponent with the .OnDestroy: IMulticastNotifyEvent - then the component destruction callbacks would be hooked twice... Probably not big deal, as the first call would remove the second call, but still looks shady...

    I guess, to make this design flexible, hooking should be moved into an explicit pluggable scaffolding, so if one method did register the hook, others would not.

    3.1) cnRemove doesn ot test for OnDestroy becoming nil, which is unusual but possible :-) :-P

  3. Stefan Glienke repo owner

    1) It's not about abusing. The point is that the solution with the interface might break code that hooks up events while holding the object reference that not yet has been assigned to an interface and I am not risking that.

    2) The implementation you linked in fact has possible issues with destruction on both side of the event.

    3) You can still exit after the inherited if Item.Data is a TComponent or not run the inherited but instead use the other way.

    3.1) I ignored that possibility on purpose.

  4. Log in to comment