OT: Event Handlers for multicast delegate not firing

OT: Event Handlers for multicast delegate not firing

Old forum URL: forums.lhotka.net/forums/t/1923.aspx


SonOfPirate posted on Thursday, December 07, 2006

I have an event that I have "attached" multiple event handlers using the following standard syntax:

myObject.PropertyChanged += new PropertyChangedEventHandler(Handle_PropertyChanged);

This occurs in four different classes - all listening for this event.  When I run the application and the event occurs, only the most recently added handler executes.

Any ideas what is going on?

Is there any way to check what handlers are attached to an event in debug mode (VS 2k5)?

 

ajj3085 replied on Thursday, December 07, 2006

As far as I know that should work.  And unfortunately I don't know how you can check what event handlers are attached.. did you trace into Bindable base to see what code is being executed?

xal replied on Thursday, December 07, 2006

Make sure you reattach handlers for non serializable listeners...
You normally do that in OnDeserialized().

Andrés

ajj3085 replied on Thursday, December 07, 2006

What determines if an event is serializable?

xal replied on Thursday, December 07, 2006

It's not the Event itself that is or isn't serializable.

In c# when you declare an event you can add a param like: [field: NonSerialized()]. (this makes the event "reset" when the object is serialized). That means that the actual delegate that run within the event are not serialized.

In vb there is no "field:" modifier for attributes, so you are forced to code a custom event (next paragraph).

If you create the events "the hard way" you can create 2 delegates, one for serializable listeners (which does get serialized) and one for non serializable listeners. When you do AddHandler (in vb), the custom event can check whether the listener is serializable or not and add it to the correct delegate.You can do a similar thing in c# I believe.
(you can find a good example of this in the csla codebase or in rocky's blog).

Sorry for the quick explanation, it's actually a long subject... :)


Andrés



SonOfPirate replied on Thursday, December 07, 2006

To answer both questions:

I did step through the code and that's how I know what's happening.  Until then I just knew the behavior wasn't right.  When stepping through I am able to see that all four handlers are added to the event but when the event is triggered, only the handler that was attached last executes.  (I even manually attached a different handler after the other four using the Command Window and that was the one executed when the event was triggered.)

There is no serialization/deserialization taking place.  I had considered that and remembered that event handlers are not restored on deserialization.  (I have reviewed the code in BindableBase for an example how this is dealt with.)  But, since there is no serialization taking place, AND, one of the four event handlers IS being triggered, I have no reason to believe this is the culprit.

The one thing I did fail to mention is that this is a web app.  The event handlers are attached when the page is first requested.  The object model which contains both the sources of the event and the objects handling them is persisted in Session between postbacks.  So, when a button is clicked on the page, a property is changed on postback that triggers the event which should be handled by all four handlers but is only being executed by the last to be added method.

It is the fact that one of the four, and that it is the last of the four, that makes this confusing.  I would assume an all or nothing result if serialization or posting back had anything to do with it.  So, beyond that, I am stumped and dead-in-the-water until I get this figured out.

Thanks for the help.

 

ajj3085 replied on Friday, December 08, 2006

Hmmm, very weird.  Did you try the same code, but in a forms applcation?  If it works there, then asp.net is somehow throwing things off.  That might help narrow in on what may be causing a problem.

SonOfPirate replied on Friday, December 08, 2006

No I haven't, but I will do just that as soon as I can steal a few minutes to set it up.

xal replied on Friday, December 08, 2006

Can we see the code for the event? How is the event declared?

As I said before, event's use delegates internally. If you wrote a "custom" event, you need to keep in mind that you need to Combine the delegates. Here's a sample:

Public Delegate Sub EmptyDelegate()

    <NonSerialized(), NotUndoable()> _
    Private d_create As EmptyDelegate

    Protected Custom Event Create As EmptyDelegate
        AddHandler(ByVal value As EmptyDelegate)
            d_create = CType([Delegate].Combine(d_create, value), EmptyDelegate)
        End AddHandler

        RemoveHandler(ByVal value As EmptyDelegate)
            d_create = CType([Delegate].Remove(d_create, value), EmptyDelegate)
        End RemoveHandler

        RaiseEvent()
            If d_create IsNot Nothing Then d_create()
        End RaiseEvent
    End Event



If your code always runs the last defined handler, it might be that it's not combining the delegates and the delegate always holds the reference to the last handler... I don't recall if this works the same way in c#.

Andrés

ajj3085 replied on Friday, December 08, 2006

Andres, good point.  I have been assuming that he's actually having problems with the PropertyChanged event.  The C# version would be:
[NonSerialized, NotUndoable]
private event EmptyDelegate d_create;

protected event EmptyDelegate Create {
    add { d_create = (EmptyDelegate)Delegate.Combine( d_create, value ); }
    remove { d_create = (EmptyDelegate)Delegate.Remove( d_create, value ); }
}

private void RaiseEvent() {
    if ( d_create != null ) {
          d_create();
    }
}
 

SonOfPirate replied on Friday, December 08, 2006

Yea, this is in C# and that is ALL completely new to me.  We are having this problem with a "custom" event.  Here's how the class that defines and triggers the event is declared:

public class myClass
{
    protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        if (SelectionChanged != null)
            SelectionChanged(this, e);
    }
 
    public event SelectionChangedEventHandler SelectionChanged;
}

And the event is declared as:

public delegate void SelectionChangedEventHandler(System.Object sender, SelectionChangedEventArgs e);

The SelectionChangedEventArgs simply inherits System.EventArgs and adds a SelectedItem property.

The only additional comments I've ever heard with respect to declaring and using events is when it comes to serialization and the need to restore handlers after deserialization as Rocky demonstrates in BindableBase.  This stuff about combining, etc. is new to me.

(BTW - I was told this is also happening with PropertyChanged but I have not verified that one.  The custom SelectionChanged event I have seen myself.)

 

 

ajj3085 replied on Friday, December 08, 2006

Pirate,

Try doing something similar to the code I posted, where you keep the actual event property private and explicity declare the public event with the add and remove handlers.  See if that solves the problem.

At this point that's the only thing I can think of, because otherwise the code you posted looks fine.. it just might be the default add remove methods for the event as you declared it only keep the last event handler which was hooked up.  The explicit add remove I posted (and Andres) will combine the delegates into one.

HTH
Andy

xal replied on Friday, December 08, 2006

You could also try declaring the event like:


public event EventHandler<SelectionChangedEventArgs> SelectionChanged;


Andrés

ajj3085 replied on Friday, December 08, 2006

Andres,

That's the recommended way now actually... but would that change the behavior of the default add remove event methods?

Andy

xal replied on Friday, December 08, 2006

I don't think so, not at all.
But he is definitely having a rare issue:
He's not using custom events, so there's no way he could be screwing up the combines. The only thing I can think of is:

-The handlers are not being added correctly.
-The compiler did something unusual for some reason.

So, even though I don't really think it makes a difference, I'm just tossing another ball in the air... :)

SonOfPirate replied on Friday, December 08, 2006

So after all that, including making all of the changes suggested and creating a simple Windows app to verify, turns out that there was a logic error deep in the code blocking the event as a result of the conditions being tested on the page post-back!  Test using different circumstances and it works just fine.  I had the logic corrected and now the original tests work as well. (Let's hear it for team development!)

To finish the train of thought that the thread has gone on, can you tell me what the advantage/disadvantages are of specifying events in the manner you've described aside from addressing the deserialization issues Rocky mentioned in the book?  Also, can you refer me to where it is written up that using EventHandler<T> is now the recommended practice versus declaring your own handler delegate?  Is this just to simplify things or is there a reason why this is now recommended?

Thanks for the follow-up.

ajj3085 replied on Friday, December 08, 2006

Glad you got it fixed.  The only advantage of havng your own add remove is that you can control it more... but thinking it would help was a long shot, I must admit.  Personally I prefer it because its more in keeping with the 'private fields, public properties' design I like to keep. 

Does Rocky recommend having explicit add / remove for your events for some deserialization issue?  I might have missed that in the book.

The recommendation to use EventHandler<T> actually comes from FxCop.  Its under Microsoft.Design, CA1003.  The reason they suggest to use it is a good one; you no longer have to define your own delegates if you have custom event arguments.

So instead of :

public delegate void MyEventHandler( object sender, MyEventArgs e );

public event MyEventHandler MyEvent;

You just do:

public event EventHandler<MyEventArgs> MyEvent;

and off you go.  Basically you no longer have to create your own delegates anymore, which is kind nice.  A little less code you have to enter and maintain.

Copyright (c) Marimer LLC