C# events need generic "Unsubscribe" method

J

Jon Davis

The garbage handler in the .NET framework is handy. When objects fall out of
scope, they are automatically destroyed, and the programmer doesn't have to
worry about deallocating the memory space for those objects. In fact, all
the programmer has to worry about is the total sum of objects loaded into
RAM at any known point. Memory leaks are not a problem.

.... So one would like to think. The reality is that delegates and event
subscriptions bring about a whole new problem. When an object subscribes to
another object's events, the "other object" will not fall out of scope until
unsubscription occurs.

For instance, if in a Windows Forms application I have a MainForm that
creates a DocumentClass with a subscription to a Reset event, the
DocumentClass will not fall out of scope when it is closed until the
MainForm unsubscribes from the DocumentClass's Reset event.

The reason why this is so is because there is a circular reference in place.
MainForm created a DocumentClass, DocumentClass added MainForm's delegate to
its event, DocumentClass is no longer in MainForm's scope, but DocumentClass
still "remembers" MainForm due to the delegate in its event subscriptions
collection and will stick around until MainForm is unloaded.

If this were not so then the months I have spent trying to kill the memory
leaks I had created by forgetting to unsubscribe from events would have had
no effect, but they did have an effect -- the [DocumentClass] objects are
now getting unloaded, whereas they previously were not.

The biggest problem with this situation is that keeping track of delegates
created for event subscriptions so that timely unsubscriptions can occur is
an incredibly arduous chore. The delegates must either be kept in a
collection and referenced when a particular event occurs or when a
particular method is executed, or the delegates must be noted in-method, the
object's asynchronous use must be waited upon in a DoEvents / Sleep loop,
and then the individually noted delegates must unsubscribe from each of the
asynchronous events.

There are ways to automate the process of subscribing / unsubscribing
to/from events, such as creating a method like this (untested) ...

public void SubscribeEvent(object subject, string eventName, delegate
subscription) {
[ ... assign subscription using Reflection ... ]
}

... and an unsubscribe method ...

public void UnsubscribeEvent(object subject, string eventName, delegate
subscription) {
[ ... check for subscription -- if exists, remove ...]
}

I have not utilized this yet because I don't know if this is the best
approach.

However, -- and this is the main reason why I'm posting this -- I personally
wish the C# language supported a method in an event's interface where you
could simply pass it a reference to a host object and all subscriptions from
that host object would be unsubscribed.

For instance, in MainForm ...

myDocumentClass.ResetEvent.Unsubscribe(this);
// Iterate through all delegate subscriptions, and if the
// delegate's owner object is same as "this", remove
// (unsubscribe).

This would save me so much pain and headache of tracking the specific
delegate instance. Granted, a delegate is more than just the subscribing
object--it is the subscribing METHOD--but no method exists outside of a
Type, and in the case of a type instance--a System.Object--this interface
would be usable only and strictly and predictably for the circumstance of a
subscribing object instance.

Mean time, I can work around this with a generic class/method, like I said
above. But the interface for this is far more elaborate and it is still a
chore than merely passing a "this" to an Unsubscribe() method on the
object's event.

Thanks for reading this. Comments and corrections are welcome.

Jon
 
A

Alvin Bruney

The garbage handler in the .NET framework is handy. When objects fall out
of
scope, they are automatically destroyed, and the programmer doesn't have to
worry about deallocating the memory space for those objects. In fact, all
the programmer has to worry about is the total sum of objects loaded into
RAM at any known point. Memory leaks are not a problem.

This is a blanket statement. It is not always true. It is mostly true,
except when you wrap unmanaged resources. It's also not true for COM interop
where references are held on the other side. I believe, based on your post,
that you realize this. Others may not, hence my 2pence.
However, -- and this is the main reason why I'm posting this -- I personally
wish the C# language supported a method in an event's interface where you
could simply pass it a reference to a host object and all subscriptions from
that host object would be unsubscribed.

True, but I think the framers of the constitution...uhh, i mean the
framework realized that putting in this code would not help the majority of
us and so it was left out simply because the owner of the object to which
the delegate is attached will automatically clean up when it goes away.
Normally, this is mostly right because for most situations, as long as the
object is around, you may need to subscribe to the event. If you choose not
to, you can unsubscribe manually. In atypical situation like yours, you are
better served implementing this generic unchaining yourself. In short, it
isn't needed in most cases and requiring the framework to implement it for
special cases is a fruitless exercise for the majority.
--


-----------
Got TidBits?
Get it here: www.networkip.net/tidbits
Jon Davis said:
The garbage handler in the .NET framework is handy. When objects fall out of
scope, they are automatically destroyed, and the programmer doesn't have to
worry about deallocating the memory space for those objects. In fact, all
the programmer has to worry about is the total sum of objects loaded into
RAM at any known point. Memory leaks are not a problem.

... So one would like to think. The reality is that delegates and event
subscriptions bring about a whole new problem. When an object subscribes to
another object's events, the "other object" will not fall out of scope until
unsubscription occurs.

For instance, if in a Windows Forms application I have a MainForm that
creates a DocumentClass with a subscription to a Reset event, the
DocumentClass will not fall out of scope when it is closed until the
MainForm unsubscribes from the DocumentClass's Reset event.

The reason why this is so is because there is a circular reference in place.
MainForm created a DocumentClass, DocumentClass added MainForm's delegate to
its event, DocumentClass is no longer in MainForm's scope, but DocumentClass
still "remembers" MainForm due to the delegate in its event subscriptions
collection and will stick around until MainForm is unloaded.

If this were not so then the months I have spent trying to kill the memory
leaks I had created by forgetting to unsubscribe from events would have had
no effect, but they did have an effect -- the [DocumentClass] objects are
now getting unloaded, whereas they previously were not.

The biggest problem with this situation is that keeping track of delegates
created for event subscriptions so that timely unsubscriptions can occur is
an incredibly arduous chore. The delegates must either be kept in a
collection and referenced when a particular event occurs or when a
particular method is executed, or the delegates must be noted in-method, the
object's asynchronous use must be waited upon in a DoEvents / Sleep loop,
and then the individually noted delegates must unsubscribe from each of the
asynchronous events.

There are ways to automate the process of subscribing / unsubscribing
to/from events, such as creating a method like this (untested) ...

public void SubscribeEvent(object subject, string eventName, delegate
subscription) {
[ ... assign subscription using Reflection ... ]
}

.. and an unsubscribe method ...

public void UnsubscribeEvent(object subject, string eventName, delegate
subscription) {
[ ... check for subscription -- if exists, remove ...]
}

I have not utilized this yet because I don't know if this is the best
approach.

However, -- and this is the main reason why I'm posting this -- I personally
wish the C# language supported a method in an event's interface where you
could simply pass it a reference to a host object and all subscriptions from
that host object would be unsubscribed.

For instance, in MainForm ...

myDocumentClass.ResetEvent.Unsubscribe(this);
// Iterate through all delegate subscriptions, and if the
// delegate's owner object is same as "this", remove
// (unsubscribe).

This would save me so much pain and headache of tracking the specific
delegate instance. Granted, a delegate is more than just the subscribing
object--it is the subscribing METHOD--but no method exists outside of a
Type, and in the case of a type instance--a System.Object--this interface
would be usable only and strictly and predictably for the circumstance of a
subscribing object instance.

Mean time, I can work around this with a generic class/method, like I said
above. But the interface for this is far more elaborate and it is still a
chore than merely passing a "this" to an Unsubscribe() method on the
object's event.

Thanks for reading this. Comments and corrections are welcome.

Jon
 
D

David

I thought I'd add just a few random thoughts to this, since you asked
for comments...
... So one would like to think. The reality is that delegates and event
subscriptions bring about a whole new problem. When an object subscribes to
another object's events, the "other object" will not fall out of scope until
unsubscription occurs.

Right. The plus side here is that you don't have to keep explicit
references to all your objects just to catch events. This is generally
the correct behavior.
For instance, if in a Windows Forms application I have a MainForm that
creates a DocumentClass with a subscription to a Reset event, the
DocumentClass will not fall out of scope when it is closed until the
MainForm unsubscribes from the DocumentClass's Reset event.

The reason why this is so is because there is a circular reference in place.
MainForm created a DocumentClass, DocumentClass added MainForm's delegate to
its event, DocumentClass is no longer in MainForm's scope, but DocumentClass
still "remembers" MainForm due to the delegate in its event subscriptions
collection and will stick around until MainForm is unloaded.

Well, but the phrase "fall out of scope" is being used pretty loosely
here. There's pretty much only two options here, I think:

One, the DocClass object reference is a local variable in a function, in
which case you *need* this event behavior in order to catch the events
after you leave the function. And in this case, your suggestion about a
new framework function doesn't really make sense (since MainForm has no
object reference to call the function on), so I'll assume we're not
talking about this case where the reference has really fallen out of
scope.

Two, the DocClass object reference is a member variable of the MainForm
object. In this case, it can't really "fall out of scope" until the
MainForm goes away, at most you could re-assign the reference variable
(possibly to null). In other words, to me it seems that you haven't
*implicitly* forgotten to delete an object (which, vaguely speaking, is
what GC is for), you're *explicitly* deleting the object reference,
which seems to be a much simpler problem to track down.

(Of course, there's the static variable option as well, but that doesn't
seem to be immediately relevant here).
If this were not so then the months I have spent trying to kill the memory
leaks I had created by forgetting to unsubscribe from events would have had
no effect, but they did have an effect -- the [DocumentClass] objects are
now getting unloaded, whereas they previously were not.

I'm definitely missing something here. Since you're explicitly deleting
the object reference, why not just ask the DocumentClass object to
kill all known delegates from the owner before you kill the reference.

In other words,

// from MainForm
myDocClassObject.Unsubscribe(this);
myDocClassObject = null;


// and in DocumentClass
public void Unsubscribe(object obj)
{
// for each relevant event
foreach(Delegate d in MyEvent.GetInvocationList())
{
if(d.Target.Equals(obj))
MyEvent -= (EventHandler) d;
}
}

OK, there might be some performance problems there if you have a ton of
events (which you could clear somewhat by specifying event names in the
call), but in general this makes the problem no more difficult than the
various classes one has to Close() after using.

Is there some other reason this won't work?

The biggest problem with this situation is that keeping track of delegates
created for event subscriptions so that timely unsubscriptions can occur is
an incredibly arduous chore. The delegates must either be kept in a
collection and referenced when a particular event occurs or when a
particular method is executed,

But the delegates *are* kept in a collection in the class that owns the
event, which is how it should be, IMHO.
However, -- and this is the main reason why I'm posting this -- I personally
wish the C# language supported a method in an event's interface where you
could simply pass it a reference to a host object and all subscriptions from
that host object would be unsubscribed.

For instance, in MainForm ...

myDocumentClass.ResetEvent.Unsubscribe(this);
// Iterate through all delegate subscriptions, and if the
// delegate's owner object is same as "this", remove
// (unsubscribe).

This would save me so much pain and headache of tracking the specific
delegate instance.

Of course, that's not going to effect the "garbage collection" aspect of
the problem, which is how this posted started. The user of a class
would still need to remember to call this function, it wouldn't happen
automatically.
 

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