[Mono-list] I must misunderstand delegates

Mike Welham mike@digitalnova.co.za
Sat, 20 Nov 2004 15:51:43 +0200


Hi Kevin,

> I'm writing a simple test (shown below) using an event with the
> accessor methods add/remove.  I am trying in the remove accessor
> method to use Delegate.RemoveAll to get rid of all occurances of
> value.  However, RemoveAll takes 2 delegate parameters, removing all
> occurances of d2's invocation list from d1's invocation list.  That
> doesn't seem to be working when I pass in value as d2.  Also, if I
> create a new Delegate with value as its method reference, and passing
> this new Delegate instance to RemoveAll, it doesn't remove either. :(

Delegates are unusual in that they're immutable. (Once they're created you
can't modify them.)

So when using Delegate.RemoveAll (or .Combine), the two args you pass in are
not modified, but the method returns a new delegate with the modifications
you've asked for.

So your "remove" should look something like this:

remove
{
  _ageChange = (AgeChangeHandler) Delegate.RemoveAll(_ageChange, value);
}

> What do I put in the add accessor to check if value is already in the
> delegate's invocation list somewhere, and only add if it is not in the
> list?

Again, the same immutability issue. 

Your "add" (as per your requirement that the same delegate mustn't be added
if it's already there) could look something like this:

add
{
  if((_ageChange == null) ||
    (Array.IndexOf(_ageChange.GetInvocationList(), value) < 0))
  {
    _ageChange = (AgeChangeHandler) Delegate.Combine(_ageChange, value);
  }
}

> if(_ageChange != null)
> {  
>    // invoke the event's delegate
>    _ageChange(_age);
> }

One more (pedantic) thing, if your events are intended to be thread-safe,
they shouldn't be fired as you have done above.

The problem is that between your null-check and the invocation the
subscribers could unsubscribe from another thread, and you'll get a
NullReferenceException thrown at the invocation.

This can be avoided by doing something like this:

AgeChangeHandler ageChange = ageChange;
if (ageChange != null)
{
  _ageChange(_age);
}

Hope this helps.

Regards

Mike