Setting an object to null vs Dispose()
Asked Answered
T

3

118

I am fascinated by the way the CLR and GC works (I'm working on expanding my knowledge on this by reading CLR via C#, Jon Skeet's books/posts, and more).

Anyway, what is the difference between saying:

MyClass myclass = new MyClass();
myclass = null;

Or, by making MyClass implement IDisposable and a destructor and calling Dispose()?

Also, if I have a code block with a using statement (eg below), if I step through the code and exit the using block, is the object disposed of then or when a garbage collection occurs? What would happen if I call Dispose() in the using block anyay?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

Stream classes (eg BinaryWriter) have a Finalize method? Why would I want to use that?

Teryn answered 22/2, 2009 at 0:26 Comment(0)
C
231

It's important to separate disposal from garbage collection. They are completely separate things, with one point in common which I'll come to in a minute.

Dispose, garbage collection and finalization

When you write a using statement, it's simply syntactic sugar for a try/finally block so that Dispose is called even if the code in the body of the using statement throws an exception. It doesn't mean that the object is garbage collected at the end of the block.

Disposal is about unmanaged resources (non-memory resources). These could be UI handles, network connections, file handles etc. These are limited resources, so you generally want to release them as soon as you can. You should implement IDisposable whenever your type "owns" an unmanaged resource, either directly (usually via an IntPtr) or indirectly (e.g. via a Stream, a SqlConnection etc).

Garbage collection itself is only about memory - with one little twist. The garbage collector is able to find objects which can no longer be referenced, and free them. It doesn't look for garbage all the time though - only when it detects that it needs to (e.g. if one "generation" of the heap runs out of memory).

The twist is finalization. The garbage collector keeps a list of objects which are no longer reachable, but which have a finalizer (written as ~Foo() in C#, somewhat confusingly - they're nothing like C++ destructors). It runs the finalizers on these objects, just in case they need to do extra cleanup before their memory is freed.

Finalizers are almost always used to clean up resources in the case where the user of the type has forgotten to dispose of it in an orderly manner. So if you open a FileStream but forget to call Dispose or Close, the finalizer will eventually release the underlying file handle for you. In a well-written program, finalizers should almost never fire in my opinion.

Setting a variable to null

One small point on setting a variable to null - this is almost never required for the sake of garbage collection. You might sometimes want to do it if it's a member variable, although in my experience it's rare for "part" of an object to no longer be needed. When it's a local variable, the JIT is usually smart enough (in release mode) to know when you're not going to use a reference again. For example:

StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();

// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);

// These aren't helping at all!
x = null;
sb = null;

// Assume that x and sb aren't used here

The one time where it may be worth setting a local variable to null is when you're in a loop, and some branches of the loop need to use the variable but you know you've reached a point at which you don't. For example:

SomeObject foo = new SomeObject();

for (int i=0; i < 100000; i++)
{
    if (i == 5)
    {
        foo.DoSomething();
        // We're not going to need it again, but the JIT
        // wouldn't spot that
        foo = null;
    }
    else
    {
        // Some other code 
    }
}

Implementing IDisposable/finalizers

So, should your own types implement finalizers? Almost certainly not. If you only indirectly hold unmanaged resources (e.g. you've got a FileStream as a member variable) then adding your own finalizer won't help: the stream will almost certainly be eligible for garbage collection when your object is, so you can just rely on FileStream having a finalizer (if necessary - it may refer to something else, etc). If you want to hold an unmanaged resource "nearly" directly, SafeHandle is your friend - it takes a bit of time to get going with, but it means you'll almost never need to write a finalizer again. You should usually only need a finalizer if you have a really direct handle on a resource (an IntPtr) and you should look to move to SafeHandle as soon as you can. (There are two links there - read both, ideally.)

Joe Duffy has a very long set of guidelines around finalizers and IDisposable (co-written with lots of smart folk) which are worth reading. It's worth being aware that if you seal your classes, it makes life a lot easier: the pattern of overriding Dispose to call a new virtual Dispose(bool) method etc is only relevant when your class is designed for inheritance.

This has been a bit of a ramble, but please ask for clarification where you'd like some :)

Contrition answered 22/2, 2009 at 9:47 Comment(7)
Re "The one time where it may be worth setting a local variable to null" - perhaps also some of the thornier "capture" scenarios (multiple captures of same variable) - but it might not be worth complicating the post! +1...Vantassel
@Marc: That's true - I hadn't even thought of captured variables. Hmm. Yeah, I think I'll leave that alone ;)Contrition
could you please tell that what will happen when you set "foo=null" in your code snippet above? As far as I know, that line only clears the value of a variable pointing to foo object in managed heap? so the question is what will happen to foo object there? shouldn't we call dispose of that?Irritability
@odiseh: If the object were disposable, then yes - you should dispose it. That section of the answer was only dealing with garbage collection though, which is entirely separate.Contrition
+1 in particular, for: "It's worth being aware that if you seal your classes, it makes life a lot easier: the pattern of overriding Dispose to call a new virtual Dispose(bool) method etc is only relevant when your class is designed for inheritance."Pandean
I was looking for a clarification on some IDisposable concerns, so I googled for "IDisposable Skeet" and found this. Great! :DPandiculation
@odiseh, after setting to null, you cannot call dispose, as the lvalue becomes null. if that object had references to memory which got de-referenced however, then those references might get added to the generation of memory soon to be cleaned up by the Garbage collector.Mandell
F
22

When you dispose an object, the resources are freed. When you assign null to a variable, you're just changing a reference.

myclass = null;

After you execute this, the object myclass was referring to still exists, and will continue to until the GC gets around to cleaning it up. If Dispose is explicitly called, or it's in a using block, any resources will be freed as soon as possible.

Finale answered 22/2, 2009 at 0:31 Comment(2)
It may not still exist after executing that line - it may have been garbage collected before that line. The JIT is smart - making lines like this almost always irrelevant.Contrition
Setting to null could mean that resources held by the object are never freed. The GC doesn't dispose, it only finalizes, so if the object directly holds unmanaged resources and its finalizer doesn't dispose (or it doesn't have a finalizer) then those resources will leak. Something to be aware of.Lombardy
C
9

The two operations don't have much to do with each others. When you set a reference to null, it simply does that. It doesn't in itself affect the class that was referenced at all. Your variable simply no longer points to the object it used to, but the object itself is unchanged.

When you call Dispose(), it's a method call on the object itself. Whatever the Dispose method does, is now done on the object. But this doesn't affect your reference to the object.

The only area of overlap is that when there are no more references to an object, it will eventually get garbage collected. And if the class implements the IDisposable interface, then Dispose() will be called on the object before it gets garbage collected.

But that won't happen immediately after you set your reference to null, for two reasons. First, other references may exist, so it won't get garbage collected at all yet, and second, even if that was the last reference, so it is now ready to be garbage collected, nothing will happen until the garbage collector decides to delete the object.

Calling Dispose() on an object doesn't "kill" the object in any way. It is commonly used to clean up so that the object can be safely deleted afterwards, but ultimately, there is nothing magical about Dispose, it's just a class method.

Chalky answered 22/2, 2009 at 0:58 Comment(2)
I think this answer compliments or is a detail to the answer of "recursive".Baltic
how to see memory usage Sir, if I have so many MB's data and want to debug this statement, is there any way to proove this condition is that true, fact or or not?Repetition

© 2022 - 2024 — McMap. All rights reserved.