tags:

views:

152

answers:

4

I have a parent object that has a reference to a child object and additionally the parent has an event handler that listens to an event of the child object.

If all references to the parent object will be released, will the memory used through the parent and the child be freed through GC? (Assuming that no more references neither to the child nor to the parent exist).

class ParentClass {

    ChildClass _childClass;

    public ParentClass(ChildClass childClass) {
        _childClass = childClass;
        childClass.SomeEvent += ChildClass_SomeEvent;
    }

    void ChildClass_SomeEvent(object sender, SomeEventArgs e) {

    }
}

Please note, I'm aware that GC does not react immediately. My question is not if the memory is freed immediatly after releasing the parent object. My question is, if the memory is released anyhow.

Update

For me it seems that the answer is a clear yes, GC is able to resolve this circular reference. But for all who read this post and have a similar question, take care to not leave event-registration open. It's only a special example in which the registration is no problem. In other cases, event-registrations may cause serious memory leaks.

A very good resource covering this question was provided by vilx: http://www.interact-sw.co.uk/iangblog/2004/07/07/circulareventrefs

+9  A: 

Yes. .NET GC handles circular references without problems (assuming that you are not using unmanaged resources or you implement IDisposable if you do).

Jaroslav Jandek
The GC can handle any referencing situation as you say, but I think you need to clarify the use of the event registration in the OPs example - what happens in this scenario? Any event handlers left registered are sources of potential memory leaks in .NET (memory leak meaning GC cannot collect).
Adam
Its a question I have for a long time. Its not specific for events, I choose the events as an example because my english is so bad that I had not the possibility to explain the real questions so that someone would understand and send an answer. Its about the circular references of two objets that are in relation to each other but have no further references. And the discustion has entered exactly to this topic.
HCL
I would then say to alter your OP to remove the event usage - as events are introducing a layer of complexity on-top of your question that arguably cannot be left unmentioned. Either way, good question :-)
Adam
>"you implement IDisposable"...and call the dispose() method manually.
mishal153
@mishal153: If GC does not resolve the circular reference, clearly I have to make something (as I already did a lot of times in many projects). But the quesion is, if its even necessary to do this in such a case. Implementing IDisposable I don't like for such a thing because it directs the user of the class in a wrong direction. IMO IDisposable is mainly used, if unmanaged resources are used. But probably thats a detail.
HCL
@HCL - GC **DOES** resolve circular references. If `a` has a reference to `b` and `b` has a reference to `a`, but there are no other references to either `a` or `b`, they will both get collected.
Vilx-
The problem with events is IMHO that many developers do not know (or ignore) the fact that events create a strong reference from source to the listener. If you know about it, you can implement countermeasures (like Weak Event Patterns, unsubscribing, ...).
Jaroslav Jandek
+3  A: 

Well, if nothing else was using the same instance of _childClass, yes it would be collected. However, I'm not sure what the implications are of you not detaching the event handler - event handlers left registered are a source of un-GC-able memory leaks in .NET applications - yours is an interesting take on the problem.

Update: turns out my understanding of the memory leak source was backwards. If class A subscribes to class B but does not unsubscribe, then A will only be collected when B is collected (because B's subscription list is keeping A alive). For long lived event sources, this can be a problem for subscribers.

The problem occurs in all object life-spans, but in reality, if B is just as short-lived as A, the leak is not noticed and not usually a problem.

Update 2: in the case of the OP's example, child is the source and parent is the subscriber - the GC can handle this situation - so long as child is not referenced outside of the parent, meaning child is not elligible for collection (this will keep the parent alive).

Adam
IMHO event handlers and delegates in general are no different than any other references. Thus, no explicit detaching is needed. Event handlers are an infamous source of "memory leaks" because people tend to forget about them, but that's about it.
Vilx-
If you register an event from class A to class B and then A is collected, you won't be able to collect B because of the dead-reference of the event handler registered by class A - this is the source of leaks in .NET. If A removes the subscription in the finalizer or otherwise before collection, then B can be collected normally too.
Adam
Uups, if I understand right, circular references are not resolved?
HCL
Event delegates contain *weak references* as far as I know, and thus do not prevent a class being garbage collected. It may be a source of memory leaks in other environments, but not that I've encountered.
Noldorin
Wait... in your example, which class (A or B) has the event and which one subscribes to it? Don't quite understand your sentence...Anyway, I think you've misunderstood something. Removing event handlers in a finalizer to allow for garbage collection goes completely against every article about the .NET GC ideology that I've ever read. Finalizers are for cleaning up unmanaged resources, not managed ones. In fact trying to write a finalizer is mostly a bad idea itselfl. Here, read this article: http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae
Vilx-
Oops, seems I'm wrong about that - such references to event handlers are strong. There's been a lot of discussion about them being weak though, and I'm certainly an advocate of that.
Noldorin
@Noldorin: I never digged realy deep into this things, but as far as I remember weak references are a newer concept and not realized with the clr events. Am I wrong?
HCL
@Noldorin and @ Vilx I'm not trying to be argumentative, but Google "C# event handler memory leaks" and you will find examples. I cannot remember any specific examples off the top of my head, the example I gave is what I *believe* to be the case. If I'm wrong, I stand to be corrected :-)
Adam
@Adam: No, it's fair enough. :) You did miss my latest comment though I think heh. There's been so much discussion about it, some of which I read a while back, I got muddled about the truth of the current state.@HCL: Weak references have been around since the CLR was conceived as far as I know. But yeah, not used with events/delegates.
Noldorin
As I see it: Suppose that an object `A` has an event `e`. Also suppose an object `B` has a method `m`. Now, if `A.e += B.m;` then: (1): If all references to `A` are lost, it is GC'ed. `B` does **NOT** hold a reference to `A`. (2): If all references to `B` are lost (except for the event), it is **NOT** GC'ed, because `A` still has a reference to `B`. (3): If all references to both of them are lost, they are both GC'ed. In other words, it's no different than if `A` had a field `p` which would be set to `B`.
Vilx-
@Vilx you *have* to use the finalizer if you cannot call Dispose (ie, you don't know when your object is elligible because you are writing an add-in to another app). Yes its for unmanaged stuff, but also for last-chance-saloon cleanup to avoid memory leaks, something that event handlers can introduce. My example, B has the event, A subscribes to B event, A is nulled thus B's invocation list has a registrant with no source. You are correct that if you, say, had a SQL connection un-closed, you still wouldn't close it from the finalizer - but this is only because the connection will be GC'd.
Adam
@Vilx no I believe its the other way around. The class with the event cannot be disposed because the event subscription list is holding onto it - hence to dispose the event container, events need to be unregistered completely.
Adam
You've got something mixed up. The "subscription list" is no different than any other collection (say, your standard ArrayList), and it's a member of the class which has the event. So if all references to that class are lost, so are references to the subscription list, and both objects are collected together. Also,googling on your keywords gave me this, completely supporting what I stated above: http://www.interact-sw.co.uk/iangblog/2004/07/07/circulareventrefs
Vilx-
Both of your links also say the same thing as I do. The container can keep the subscribers alive; the subscribers cannot keep the container alive.
Vilx-
@Vilx as I've mentioned, I'm unsure on the specifics - but memory leaks from registered events left registered **do** occur. However, I'll admit I've no idea how this problem behaves with a parent-child relationship.
Adam
@Vilx ah I see, so I was mis-understanding the direction of the problem - I'll update my answer.
Adam
There is a common leak, but it's this pattern:`A.e += B.m; B = null; GC.Collect();` And here people often wonder why `B` isn't collected. It's a typical scenario, easy to miss. I've had bugs like that too (was pretty "fun" to hunt it down...) But it doesn't work the other way round, that's a myth spread by people who don't really understand what this is all about.
Vilx-
@Vilx well I've updated my answer now, so I'm no longer guilty of spreading the myth. However its still dangerous, people often don't know that *either* exist - the myth or the truth. The one that highlighted it to me was a university that used C# to drive a car, after 3 hours it would fail - memory leak slowly building up from hung subscribers lol
Adam
Thank you! Now I feel like I've done my _good deed of the day_. :D
Vilx-
Do another good deed and drop the downvote lol, well, if it was you ;-)
Adam
Nop, not me. :)
Vilx-
+1  A: 

When there are no more strong references to a particular object on the heap (i.e. reference count has gone to zero), the GC is indeed scheduled to release memory for the object. It doesn't matter where or how these references are defined; they are tracked by the GC and handled accordingly.

Worth noting however: the GC is very non-deterministic way by nature (it runs on a separate thread, for a start), and may not get around to destroying the object in memory for some time. "Some time" is usually almost instantaneous, except in unusual cases where the processor is too busy and/or a lot of memory is being freed up at once.

Noldorin
And as Joralsav Jandek wrote, GC is aware of the circular-reference and does not count the child as a reference count?
HCL
In the example you presented there is no circular reference, as I see it... Even if there were, the GC doesn't really have to handle circular references specially, as I know. A reference count for each object is quite sufficient.
Noldorin
Parent references the child and in the event-list of the child is a reference to the parent, to inform it about SomeEvent. Is seems to me as circular. Am I wrong?
HCL
Oh sorry, I ignored where the event handler was defined. So it is circular in that respect, though it doesn't really make a difference as I've said. See my comment on Adam's post please.
Noldorin
+1  A: 
childClass.SomeEvent += ChildClass_SomeEvent;

This code means, that child has reference to parent. This is circular reference, GC can handle this. Unsubscribed event can be dangerous in another case. Let's say that instead of childClass you have some independent instance:

anotherClass.SomeEvent += AnotherClass_SomeEvent;

Now, when you think that ParentClass may be collected, it actually remains alive, if anotherClass still exists and event subscription is not canceled. Only when anotherClass is collected, ParentClass may be collected as well.

Alex Farber
Yes, Im aware of the scenario you describe
HCL