How to check if two method references are referencing same method?
Asked Answered
C

1

18

I am trying to make list of event handlers where handler is method reference. To delete specific handler i need to find it in the list. But how can i compare code address of two method references?

type
  TEventHandler = reference to procedure;

procedure TestProc;
begin
end;

procedure TForm26.FormCreate(Sender: TObject);
var
  Handlers: TList<TEventHandler>;
begin
  Handlers := TList<TEventHandler>.create;
  try
    Handlers.Add(TestProc);
    Handlers.Remove(TestProc); { doesn't work }
    Assert(Handlers.Count=0);  { fails }
    Assert(Handlers.IndexOf(TestProc)>=0); { fails }
  finally
    FreeAndNil(Handlers);
  end;
end;

Default comparer of TList<> doesn't compare method references properly. How can i compare them? Is there structure similar to TMethod but for method references?

Calumniate answered 26/10, 2016 at 11:18 Comment(11)
TEqualityComparer<TProc>.Default.Equals(A, B)Brain
And you can use the TProc instead of your own declaration... just add System.SysUtils.Brain
@ZENsas I know about TProc, i just tried to make example as clear as possible. TEqualityComparer<TProc>.Default.Equals(A, B) doesn't work, i just tested that (otherwise TList<>.Remove will work too, it is based on default comparer).Calumniate
#5153986Brain
TEqualityComparer<TProc>.Default.Equals(A, B) DOES work. You are just doing the things in a specific way. You want to compare the actual code of the methods. So method comparison works properly. You just want to do different thing.Brain
@ZENsan TList<T> does not use TEqualityComparer<T> but TComparer<T> but that does not matter as both fail because they just compare references (pointers). And the references passed to Add and Remove are different as I explained in my answer.Wobbly
He can create the TList with his own comparer constructed.. Do not see any problem.Brain
@ZENsan You are missing the point. The list already uses the default comparer. It regards these two anon methods as different because, as anon methods, they are different.Thracian
@DavidHeffernan. As I mentioned. Author is trying to compare the actual code of the methods. If you do x1, x2: TProc; then X1 := procedure begin ... end; and then x2 := x1; In this case x1 is equal to x2 and equality comparer does return that x1 = x2. Stefan Glienke answered absolutely correctly on this question.Brain
@ZENsan I understand this all very clearly, and I have no confusion over what the asker is trying to do. You don't need to explain it to me. Supplying a different comparer won't change anything. You have completely missed the issue in your comments thus far. Read Stefan's answer to understand the issue. Stefan's first comment above explains also what you have missed.Thracian
I apologize for not reading the question carefully enough.Brain
W
14

This is not as easy as it might seem.

To understand why this happens you need to understand how assigning to a method reference is performed by the compiler.

The code you wrote is basically translated into this by the compiler:

Handlers.Add(procedure begin TestProc; end);
Handlers.Remove(procedure begin TestProc; end);

Now we have to know that if you have multiple anonymous methods within the same routine they are in fact different anonymous methods even if their code is identical. (see How are anonymous methods implemented under the hood?)

This means that the values passed to Add and Remove are different even if the code in their bodies is the same - even with hacking around it would require a binary code analysis to determine if the code inside the body is the same.

If you would change the code as follows it would work because then you only have one anonymous method - for this snipped it works but usually you would not add and remove within the exact same routine:

var
  Handlers: TList<TEventHandler>;
  Handler: TEventHandler;
begin
  Handlers := TList<TEventHandler>.create;
  try
    Handler := TestProc;
    Handlers.Add(Handler);
    Handlers.Remove(Handler);
    Assert(Handlers.Count=0);
  finally
    FreeAndNil(Handlers);
  end;
end;

If you want a list where you add and remove event handlers my personal recommendation is to avoid an anonymous method type and use procedure or methods:

type
  TEventHandlerA = procedure;
  TEventHandlerB = procedure of object;

The decision which one is better is up to you because you know your code better.

Wobbly answered 26/10, 2016 at 11:40 Comment(2)
I didn't expect that anonymous method is generated when i call something with method reference as parameter. I supposed that i can use method references without anonymous methods actually. Thank you!Calumniate
@AndreiGalatyn: you can use procedure of object references without anonymous methods, but you can't use reference to procedure references without anonmeths, since the latter are anonmeth references.Sylvia

© 2022 - 2024 — McMap. All rights reserved.