Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem with event "chaining"

Note: I edited this question to make it easier for other people with the same problem to get help here. To see the original question that fits better with some of the answers, check the edit history.

In a project I have a ExecutionManager class that can contain multiple ExecutionSlot's instances. The ExecutionSlot class have several public event fields like this:

public event EventHandlers.ObjectEventHandler<IPlugin> ExecuteCompleted;

For each of these events there is a matching event on ExecutionManager. The desired behavior is that every time on of the ExecutionSlot's raises an event, the matching event is also raised on the containing ExecutionManager.

The implmented solution was that whenever an ExecutionSlot was added to an ExecutionManager the ExectionManager would add its own events to the ExecutionSlot's like this:

executionSlot.ExecuteCompleted += ExecuteCompleted;

There is no need yet to remove an ExecutionSlot, so the events are never removed either.

The problem is that the event on ExecutionManager is not being raised. After confirming that an event was being reaised by an ExecutionSlot I found out that changing the above line to the following fixed the problem:

executionSlot.ExecuteCompleted += (sender, eventArgs) => ExecuteCompleted(sender, eventArgs);

And I could not figure out why, so my question was, what the difference was.

The reason for this difference was that the first adds the current listeners of the ExecutionManager's event to the ExecutionSlot's event. So any listeners added later will not be called when the event is raised. In contrast the latter solution uses a lambda to raise the ExecutionManager's event, which means that the listeners at the time of the event will be called.

The underlying reason for the first solution to fail, is that delegates are immutable. So when you add a new delegate to and event, you are actually creating a new delegate that contains the existing delegates and the added. So any reference to the delegates made before will not contain the newly added delegate.

like image 274
Jacob Poul Richardt Avatar asked Jan 07 '10 16:01

Jacob Poul Richardt


2 Answers

One idea... perhaps there is somewhere in your code where you are doing:

executionSlot.ExecuteCompleted -= ExecuteCompleted;

which would unsubscribe the event if you use your original subscription syntax, but would not remove it once you made your change.

like image 110
JoelFan Avatar answered Oct 05 '22 10:10

JoelFan


EDIT: This answer was assuming that ExecuteCompleted was a method. As it's actually a field, that changes things completely. I'll leave this answer here for the sake of posterity.

The first version adds an event handler with a delegate created from an autogenerated method which in turn just calls ExecuteCompleted. It's a bit like this:

private void <>AutogeneratedMethodWithUnspeakableName(object sender, EventArgs e)
{
    ExecuteCompleted(e);
}
...
executionSlot.ExecuteCompleted += <>AutogeneratedMethodWithUnspeakableName;

The second version adds an event handler with a delegate created directly from the ExecuteCompleted method.

Basically the first form is one extra level of redirection. This wouldn't usually make any difference, except for unsubscription as JoelFan mentioned. I would guess that's the problem.

The class raising the event could reflect over the attached handlers and look at the method names, reacting differently in this particular case - but it's very unlikely.

like image 31
Jon Skeet Avatar answered Oct 05 '22 10:10

Jon Skeet