Clearing all event handler attached to an event

N

Navaneeth.K.N

Hi all,

Recently I found an interesting question on C# forums about clearing event
handlers of an event. I tried to give it a solution, but failed. I am
interested to know how you guys take this. Here it goes

class Product
{
public event EventHandler ProductChanged;

public Product(string name) {
this.name = name;
}

private string name;
public string Name
{
get { return name; }
set {
name = value;
if (ProductChanged != null)
ProductChanged(this, EventArgs.Empty);
}
}
}

"ProductChanged" event will have many subscribers. I need to clear all these
subscribers from outside "Product" class. Reason is, I am not allowed to
change the "Product" class. I am attaching the event handlers like this,

Product first = new Product("First product");
first.ProductChanged += new EventHandler(first_ProductChanged);

Product second = new Product("Second product");
second.ProductChanged += new EventHandler(second_ProductChanged);

// need to clear the ProductChanged event handlers here
// I can't get the invocation list of "Product.ProductChanged" event here.

is there anyway to achieve this? I think some kind of reflection can do
that, but I am not sure how to go about it.

Any help would be great
 
I

Israel

"ProductChanged" event will have many subscribers. I need to clear all these
subscribers from outside "Product" class. Reason is, I am not allowed to
change the "Product" class. I am attaching the event handlers like this,

Can you insert a proxy class that looks exactly like the Product class
and contains a reference to it? Then you could manage your own
delegate list via the standard add/remove methods and just subscribe
to the Product event once and when Clear() is called just clear our
your list and unsubscribe to the Product class's event. This would
require that everyone go through the proxy vs. the Product class.
 
N

Navaneeth.K.N

Peter,

That was a perfect answer. Thanks for that.

BTW, will unregistered events won't get collected on GC cycles?
 
K

Kasim Husaini

Hi Peter,

your reply was perfect for a situation where the Subscribed class is only
called from one subscriber.

In my particular situation I have a class A which has few events defined,
Class B, class C ... calls same instance of class A and adds an event handler
individually. I have not control over either Class A or B, C...
I need to clear all the event handlers for the instance of the class A.

Thanks for your support in advance

Kasim Husaini

Peter Duniho said:
[...]
// need to clear the ProductChanged event handlers here
// I can't get the invocation list of "Product.ProductChanged" event
here.

is there anyway to achieve this? I think some kind of reflection can do
that, but I am not sure how to go about it.

Reflection would not be at all reliable. The field backing the event
could be modified with reflection, but the name of the field could change
at any time. Even if you could rely on the name, it's a _really_ bad idea
to go around mucking about the internals of some class. If you're not
allowed to modify the class itself to support what you want, then you
definitely have no business poking around the class's internals. That's
bad design and a maintenance nightmare.
Any help would be great

IMHO, the only correct way to do it is to keep your own list of all the
delegates you've subscribed to the event, so that when you want to remove
them all, you can do that.

One way to effectively accomplish this is to actually duplicate the event
yourself in your own class, subscribe a single event handler to the
Product class, and then forward the event to your own event. When you
want to clear your own event, you can just set it to null (from within the
class declaring the event, where you are actually setting the event's
delegate field to null).

In your case, however, it looks like you want to apply this to the same
event on multiple instances of the same type. So you'll need to combine
the above approach with a convenient way to map each instance to the
delegate used for the event. A Dictionary<> instance can be used for that.

For example (error-checking removed for clarity, uncompiled code):

class MyClass
{
private Dictionary<Product, EventHandler> _dictEventForwarder =
new Dictionary<Product, EventHandler>();

private void _Subscribe(Product product, EventHandler handler)
{
EventHandler handlerOld;

if (_dictEventForwarder.TryGetValue(product, out handlerOld))
{
handlerOld += handler;
}
else
{
handlerOld = handler;
product.ProductChanged += _ForwardingHandler;
}

_dictEventForwarder[product] = handlerOld;
}

private void _Unsubscribe(Product product, EventHandler handler)
{
EventHandler handlerNew = _dictEventHandler[product] - handler;

if (handlerNew == null)
{
_dictEventHandler.Remove(product);
product.ProductChanged -= _ForwardingHandler;
}
else
{
_dictEventHandler[product] = handlerNew;
}
}

private void _Clear(Product product)
{
_dictEventHandler.Remove(product);
product.ProductChanged -= _ForwardingHandler;
}

private void _ForwardingHandler(object sender, EventArgs e)
{
_dictEventForwarder[(Product)sender](sender, e);
}
}

You'll note that to subscribe/unsubscribe handlers to the event, you need
to go through the special methods for the purpose, rather than doing it
directly. They wind up subscribing/unsubscribing a single method that
looks up the appropriate delegate for each instance's event and invokes
that delegate when the instance's event is raised.

Hope that helps.

Pete
 
K

Kasim Husaini

Hi Peter,

your reply was perfect for a situation where the Subscribed class is only
called from one subscriber.

In my particular situation I have a class A which has few events defined,
Class B, class C ... calls same instance of class A and adds an event handler
individually. I have not control over either Class A or B, C...
I need to clear all the event handlers for the instance of the class A.

Thanks for your support in advance

Kasim Husaini

Peter Duniho said:
[...]
// need to clear the ProductChanged event handlers here
// I can't get the invocation list of "Product.ProductChanged" event
here.

is there anyway to achieve this? I think some kind of reflection can do
that, but I am not sure how to go about it.

Reflection would not be at all reliable. The field backing the event
could be modified with reflection, but the name of the field could change
at any time. Even if you could rely on the name, it's a _really_ bad idea
to go around mucking about the internals of some class. If you're not
allowed to modify the class itself to support what you want, then you
definitely have no business poking around the class's internals. That's
bad design and a maintenance nightmare.
Any help would be great

IMHO, the only correct way to do it is to keep your own list of all the
delegates you've subscribed to the event, so that when you want to remove
them all, you can do that.

One way to effectively accomplish this is to actually duplicate the event
yourself in your own class, subscribe a single event handler to the
Product class, and then forward the event to your own event. When you
want to clear your own event, you can just set it to null (from within the
class declaring the event, where you are actually setting the event's
delegate field to null).

In your case, however, it looks like you want to apply this to the same
event on multiple instances of the same type. So you'll need to combine
the above approach with a convenient way to map each instance to the
delegate used for the event. A Dictionary<> instance can be used for that.

For example (error-checking removed for clarity, uncompiled code):

class MyClass
{
private Dictionary<Product, EventHandler> _dictEventForwarder =
new Dictionary<Product, EventHandler>();

private void _Subscribe(Product product, EventHandler handler)
{
EventHandler handlerOld;

if (_dictEventForwarder.TryGetValue(product, out handlerOld))
{
handlerOld += handler;
}
else
{
handlerOld = handler;
product.ProductChanged += _ForwardingHandler;
}

_dictEventForwarder[product] = handlerOld;
}

private void _Unsubscribe(Product product, EventHandler handler)
{
EventHandler handlerNew = _dictEventHandler[product] - handler;

if (handlerNew == null)
{
_dictEventHandler.Remove(product);
product.ProductChanged -= _ForwardingHandler;
}
else
{
_dictEventHandler[product] = handlerNew;
}
}

private void _Clear(Product product)
{
_dictEventHandler.Remove(product);
product.ProductChanged -= _ForwardingHandler;
}

private void _ForwardingHandler(object sender, EventArgs e)
{
_dictEventForwarder[(Product)sender](sender, e);
}
}

You'll note that to subscribe/unsubscribe handlers to the event, you need
to go through the special methods for the purpose, rather than doing it
directly. They wind up subscribing/unsubscribing a single method that
looks up the appropriate delegate for each instance's event and invokes
that delegate when the instance's event is raised.

Hope that helps.

Pete
 
O

ozbear

All that said, you should be sure at the outset that you really need to
clear the subscribers in the first place. Usually, the class that
subscribed is the only one that might potentially be affected by remaining
subscribed, and it should unsubscribe its own handler when it no longer
needs to be subscribed. The most common scenario I've seen a person claim
they need to clear out the subscribers is when the class declaring the
event is being discarded, and in that case, the claim is actually wrong,
as there's no need to clear the subscribers for an event in a class when
that class is being discarded; it can be garbage-collected just fine
without such an operation.

Pete
Pete,
I thought that claim was correct since if X subscribes to Y's event
then X has a reference to Y, and is Y is discarded it would not be
able to be GC'ed because of the reference. You say this is wrong
so clearly my understanding of who has a reference to whom is flawed
and I would appreciate you clearing it up. I recently ran across this
issue in some code I was writing and want to ensure I am both clearing
references I need to and not over-engineering things to account for
issues that (apparently) do not matter.

When X subscribes to Y in, for example,
Y.someevent += X.somehandler;
I thought that in addition to X.somehandler being added to
Y.someevent's invocation list, that X had a reference to Y.

Perhaps my thinking is backwards and that it is actually X that
cannot be GC'ed as long as Y lives because if Y's invocation list
reference back to X, making it somewhat imperative that X
unsubscribe any handlers it has to other classes' events in, say,
X's Dispose method. Or is that also unnecessary?

Any insight gratefully received.

Regards, Oz
 
O

ozbear

All that said, you should be sure at the outset that you really need to
clear the subscribers in the first place. Usually, the class that
subscribed is the only one that might potentially be affected by remaining
subscribed, and it should unsubscribe its own handler when it no longer
needs to be subscribed. The most common scenario I've seen a person claim
they need to clear out the subscribers is when the class declaring the
event is being discarded, and in that case, the claim is actually wrong,
as there's no need to clear the subscribers for an event in a class when
that class is being discarded; it can be garbage-collected just fine
without such an operation.

Pete
Pete,
I thought that claim was correct since if X subscribes to Y's event
then X has a reference to Y, and is Y is discarded it would not be
able to be GC'ed because of the reference. You say this is wrong
so clearly my understanding of who has a reference to whom is flawed
and I would appreciate you clearing it up. I recently ran across this
issue in some code I was writing and want to ensure I am both clearing
references I need to and not over-engineering things to account for
issues that (apparently) do not matter.

When X subscribes to Y in, for example,
Y.someevent += X.somehandler;
I thought that in addition to X.somehandler being added to
Y.someevent's invocation list, that X had a reference to Y.

Perhaps my thinking is backwards and that it is actually X that
cannot be GC'ed as long as Y lives because if Y's invocation list
reference back to X, making it somewhat imperative that X
unsubscribe any handlers it has to other classes' events in, say,
X's Dispose method. Or is that also unnecessary?

Any insight gratefully received.

Regards, Oz
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top