delphispring4d

Spring4d How to make IEvent handle reference to procedure?


when implementing an Event with the definition below Spring4D will add and invoke method but will not remove handler ( with IEvent<TaskItemChangeEvent>.Remove(MyProc) ) when asked as it does not identify it.

  {$M+}
  TaskItemChangeEvent = reference to procedure(const TaskItem: ITaskItem; Event: TTaskListEvent);

The following does work but I do not want to be forced to be bound to an object.

  {$M+}
  TaskItemChangeEvent = procedure(const TaskItem: ITaskItem; Event: TTaskListEvent) of Object;

I believe the issue is this line in TEventBase.Remove as a reference to procedure is not a TMethod?

  if TMethod(handlers[i]) = TMethod(handler) then

Solution

  • The reason is the compiler possibly creating different instances of the anonymous method between the place where you add and where you remove them.

    Look at the following code:

    var
      proc: TProc;
    
    procedure Add(p: TProc);
    begin
      proc := p;
    end;
    
    procedure Remove(p: TProc);
    begin
      Writeln(PPointer(@proc)^ = PPointer(@p)^);
    end;
    
    procedure A;
    var
      p: TProc;
    begin
      p := procedure begin end;
      Add(p);
      Remove(p);
    end;
    
    procedure B;
    begin
      Add(procedure begin end);
      Remove(procedure begin end);
    end;
    
    procedure C;
    begin
      Add(A);
      Remove(A);
    end;
    
    begin
      A;
      B;
      C;
      Readln;
    end.
    

    You will notice that in B and C it will print False because the two anonymous methods being passed to Add and Remove differ from each other. While in B it's obvious in C it is not but the compiler actually transforms the code into this:

    procedure C;
    begin
      Add(procedure begin A(); end);
      Remove(procedure begin A(); end);
    end;
    

    That means if you want to use IEvent<> with a method reference type and be able to remove you need to keep the reference that you added in order for them to be equal and thus be able to be found when calling Remove.

    The fact that internally in TEventBase the references are all handled as TMethod has nothing to do with that - when passing an anonymous method it is being transformed into a TMethod. After all an anonymous method type is an interface being backed by an object which the compiler creates which makes it possible to do such conversion and causes the necessity to keep the reference that was added in order to remove it.