Delegate comparison with WeakAddressOf

The following code does not work as I’d expect it to - it appears that when using Delegates using AddressOf, the equality operator works as expected (foo=bar), but when using WeakAddressOf, foo <> bar.

Bug?


Sub Action()
  dim foo as CallbackMethod 
  dim bar as CallbackMethod 
  
  foo = AddressOf FooBar
  bar = AddressOf FooBar
  if foo=bar then
    MsgBox "foo = bar when using AddressOf"    // THIS IS TRUE
  else 
    MsgBox "foo <>= bar when using AddressOf"
  end if
  
  
  
  foo = WeakAddressOf FooBar
  bar = WeakAddressOf FooBar
  if foo=bar then
    MsgBox "foo = bar when using WeakAddressOf"    
  else
    MsgBox "foo <> bar when using WeakAddressOf"  // THIS IS TRUE
  end if
  
End Sub

I’ve submitted this as an Inquiry, just in case. I think it is a bug, but am not sure.
<https://xojo.com/issue/27552>

Related issue: https://forum.xojo.com/943-weakdelegate-nil-but-method-is-gone/ there’s no way to tell if a Weak delegate is invalid.

So how this affects me is that I’ve written a generic “Timer callback” object - you can pass it a delegate, and it sets up a timer and calls the method when the timer fires. Works great with strong delegates, and has a method that will cancel all pending timers for a given method, which also works.

When I started to try using Weak delegates, this mechanism failed: I’m no longer able to Cancel an existing timer, because I can’t tell if a the weak delegate that’s being requested to be cancelled matches the weak delegate on file. So, the timer is not canceled and it ends up firing.

Now, since I’m using a Weak delegate, you’d think that would be OK - however, trying to .Invoke() a weak delegate gives a NilObjectException.

I can probably work around this by using strong delegates and canceling them in the window’s CancelClose event rather than the Close event, but this is only a band-aid patch…

A workaround for this (not fully tested) is to assign the delegates to Ptrs and compare those

foo = WeakAddressOf FooBar bar = WeakAddressOf FooBar dim p1 As Ptr = foo dim p2 As Ptr = bar if p1 = p2 then Msgbox "=" else Msgbox "<>" //messages =

You can’t cast delegates to Ptrs inline, must implicitly assign them as above. So maybe make a method to clean it up where the casting happens across the parameter line

Function weakDelegatesEqual(d1 As Ptr, d2 As Ptr) As Boolean return d1 = d2 End Function

For this you could also store a WeakRef to the object. When .Value goes nil assume the associated weak delegate is invalid.

Or enclose in a try/catch, ugs :stuck_out_tongue:

[quote=15573:@doofus]For this you could also store a WeakRef to the object. When .Value goes nil assume the associated weak delegate is invalid.

Or enclose in a try/catch, ugs :P[/quote]

Good tips, I may try those.

I’ve also stumbled upon another oddity with delegates, which I’ll report in a new feedback case if I can isolate it. It appears that some combination of:

  • being in a class destructor
  • having a delegate on the call stack
  • having a delegate which points to the object being destroyed
  • being in a Window.Close event

When all happening together, trigger a weird infinite loop where the object’s destructor is called over and over. Naturally it only happens in a complex application, not in a simple test case. Anyone have any ideas about this?

[quote=15572:@doofus]A workaround for this (not fully tested) is to assign the delegates to Ptrs and compare those

foo = WeakAddressOf FooBar bar = WeakAddressOf FooBar dim p1 As Ptr = foo dim p2 As Ptr = bar if p1 = p2 then Msgbox "=" else Msgbox "<>" //messages =

You can’t cast delegates to Ptrs inline, must implicitly assign them as above. So maybe make a method to clean it up where the casting happens across the parameter line

Function weakDelegatesEqual(d1 As Ptr, d2 As Ptr) As Boolean return d1 = d2 End Function[/quote]

I’d recommend against casting a delegate that came from an instance method to a pointer. There’s nothing useful you can do with that pointer (other than compare it with another pointer, apparently), so it might raise an exception in the future. The rationale behind the exception is that I’ve seen a lot of people try to pass instance methods to declares and crashes ensuing.

Yes, that’s all I meant it for, as a workaround to the delegate comparison problem.

You mean delegates will no longer cast to Ptrs?

Why do declares need Shared methods? I’ve run into this before and eventually found they need to be Shared but I don’t know why.

No, only delegates created from instance methods.

They need to either be shared or module methods. Instance methods have an implicit object parameter, self, before all of the other parameters.

Oh, I see. Thanks. It’s the same pattern as when writing a declare to instance methods vs non-instance.