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
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