8
votes

Myself and a colleague are having a difference of opinion on when an object can be garbage collected in .NET. Take the following code:

Stream stream=getStream();
using(var request=new Request(stream))
{
    Stream copy=request.Stream;

    // From here on can "request" be garbage collected?

    DoStuff1();
    DoStuff2(copy);
}

My colleague claims that when a release build is run using the server garbage collector that it is valid for the request object to be garbage collected after the call to request.Stream. He asserts that this only happens with the server garbage collector, and never with the workstation garbage collector.

The reason this is that the Request class had a finalizer that was closing the Stream given to the request. As a result, when DoStuff2 went to use the stream it got an "object disposed" exception. Since the finalizer can only be run by the garbage collector my colleague says that a garbage collection must have taken place before the end of the finally block, but after the last use of request

However, it's my belief that since the above code is just shorthand for something like this:

Stream stream=getStream();
Request request=null;

try
{
    Stream copy=request.Stream;

    // From here on can "request" be garbage collected?

    DoStuff1();
    DoStuff2(copy);
}
finally
{
    if(request!=null)
        request.Dispose();
}

Then request cannot be garbage collected after the call to request.Stream at it is still reachable from the finally block.

Also, if it were possible for the garbage collector to collect the object then the finally block would potentially exhibit undefined behavior as Dispose would be called on a GC'd object, which doesn't make sense. Likewise, it isn't possible to optimize away the finally block as an exception could be thrown within the try/using block before any garbage collection had taken place, which would require the finally block to execute.

Ignoring the issue of closing the stream in the finalizer, is it ever possible for the garbage collector to collect the object before the end of the finally block, and in effect optimize away the logic in the finally block?

2
You are correct, request cannot be GCed until the finally has run.Ben Robinson
Note, that if request.Dispose does not use instance fields request can be collected before Dispose is called on it. That's a rare circumstance.usr
Advice: Don't use finalizers. Then, this question becomes moot. You cannot detect the GC at work (except through very unusual means such as finalizers or debugging APIs). Why would Request.Finalize need to close the stream? The stream itself has a finalizer.usr
@usr - sure. However, our Dispose method is doing stuff!Sean
@HansPassant - I understand that. My questions is is there ever an scenario or optimization whereby the garbage collector ever GC the request object before the end of the using/finally block? The ODE is caused by a badly coded finalizer in the Request object, but my point is it should never run the finalizer until the end of the using block and then only after Dispose.Sean

2 Answers

10
votes

There's quite a bit going on in this question, so I'll tackle the overarching issues first.

  1. The variable declared in a using statement will not be garbage collected before the end of the block, for exactly the reason you indicated - a reference is held in order to call Dispose() in the implicit finally block.

  2. If you find yourself writing a finalizer in C#, you are probably doing something wrong. If your finalizer in C# calls Stream.Dispose(), you are definitely doing something wrong. Outside of an implementation of .NET itself, I have seen hundreds of misused finalizers, and exactly 1 finalizer that was actually needed. For more information, see DG Update: Dispose, Finalization, and Resource Management.

  3. ObjectDisposedException has nothing to do with finalization. This exception generally occurs when code calls Dispose() on an object (not finalize), and then a later call is made to do something with the object.

    Sometimes it's not obvious when code is disposing of a Stream. One case that surprised me was using StreamContent as part of sending an HTTP request using HttpClient. The implementation calls Stream.Dispose() after sending the request, so I had to write a wrapper Stream class called DelegatingStream to preserve our library's original behavior during the conversion from HttpWebRequest to HttpClient.

One scenario where you could see an ObjectDisposedException is if the getStream() method caches a Stream instance and returns it for multiple future calls. If Request.Dispose() disposes of the stream, or if DoStuff2(Stream) disposes of the stream, then the next time you attempt to use the stream you'll get an ObjectDisposedException.

7
votes

Per the language specification, section 3.9: "If the object, or any part of it, cannot be accessed by any possible continuation of execution, other than the running of destructors, the object is considered no longer in use, and it becomes eligible for destruction." using introduces a Dispose call in a finally block (section 8.13), which is something other than running a destructor, and must execute except in circumstances when even finally blocks don't execute (which the specification doesn't cover, and usually only happens when your entire AppDomain is on its way to the graveyard anyway).

An object in a using block isn't eligible for GC. In your example, request is not eligible for destruction until after the using statement, irrespective of whether it's used in the rest of the block. The only corner case (as mentioned in the comments by others) is when your Dispose implementation makes no use of its this parameter. In that case, the Dispose would not prevent the object from being eligible for destruction -- but if that happens, the question of when it can be collected is moot (as it should be -- garbage collection, when implemented correctly, should have no observable effect on a correctly implemented program).