问题
The .NET IDisposable Pattern implies that if you write a finalizer, and implement IDisposable, that your finalizer needs to explicitly call Dispose. This is logical, and is what I've always done in the rare situations where a finalizer is warranted.
However, what happens if I just do this:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
and don't implement a finalizer, or anything. Will the framework call the Dispose method for me?
Yes I realise this sounds dumb, and all logic implies that it won't, but I've always had 2 things at the back of my head which have made me unsure.
Someone a few years ago once told me that it would in fact do this, and that person had a very solid track record of "knowing their stuff."
The compiler/framework does other 'magic' things depending on what interfaces you implement (eg: foreach, extension methods, serialization based on attributes, etc), so it makes sense that this might be 'magic' too.
While I've read a lot of stuff about it, and there's been lots of things implied, I've never been able to find a definitive Yes or No answer to this question.
回答1:
The .Net Garbage Collector calls the Object.Finalize method of an object on garbage collection. By default this does nothing and must be overidden if you want to free additional resources.
Dispose is NOT automatically called and must be explicity called if resources are to be released, such as within a 'using' or 'try finally' block
see http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx for more information
回答2:
I want to emphasize Brian's point in his comment, because it is important.
Finalizers are not deterministic destructors like in C++. As others have pointed out, there is no guarantee of when it will be called, and indeed if you have enough memory, if it will ever be called.
But the bad thing about finalizers is that, as Brian said, it causes your object to survive a garbage collection. This can be bad. Why?
As you may or may not know, the GC is split into generations - Gen 0, 1 and 2, plus the Large Object Heap. Split is a loose term - you get one block of memory, but there are pointers of where the Gen 0 objects start and end.
The thought process is that you'll likely use lots of objects that will be short lived. So those should be easy and fast for the GC to get to - Gen 0 objects. So when there is memory pressure, the first thing it does is a Gen 0 collection.
Now, if that doesn't resolve enough pressure, then it goes back and does a Gen 1 sweep (redoing Gen 0), and then if still not enough, it does a Gen 2 sweep (redoing Gen 1 and Gen 0). So cleaning up long lived objects can take a while and be rather expensive (since your threads may be suspended during the operation).
This means that if you do something like this:
~MyClass() { }
Your object, no matter what, will live to Generation 2. This is because the GC has no way of calling the finalizer during garbage collection. So objects that have to be finalized are moved to a special queue to be cleaned out by a different thread (the finalizer thread - which if you kill makes all kinds of bad things happen). This means your objects hang around longer, and potentially force more garbage collections.
So, all of that is just to drive home the point that you want to use IDisposable to clean up resources whenever possible and seriously try to find ways around using the finalizer. It's in your application's best interests.
回答3:
There's lots of good discussion already here, and I'm a little late to the party, but I wanted to add a few points myself.
- The Garbage collecter will never directly execute a Dispose method for you.
- The GC will execute finalizers when it feels like it.
- One common pattern that is used for objects that have a finalizer is to have it call a method which is by convention defined as Dispose(bool disposing) passing false to indicate that the call was made due to finalization rather than an explicit Dispose call.
- This is because it is not safe to make any assumptions about other managed objects while finalizing an object (they may have already been finalized).
class SomeObject : IDisposable {
IntPtr _SomeNativeHandle;
FileStream _SomeFileStream;
// Something useful here
~ SomeObject() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
}
protected virtual void Dispose(bool disposing) {
if(disposing) {
GC.SuppressFinalize(this);
//Because the object was explicitly disposed, there will be no need to
//run the finalizer. Suppressing it reduces pressure on the GC
//The managed reference to an IDisposable is disposed only if the
_SomeFileStream.Dispose();
}
//Regardless, clean up the native handle ourselves. Because it is simple a member
// of the current instance, the GC can't have done anything to it,
// and this is the onlyplace to safely clean up
if(IntPtr.Zero != _SomeNativeHandle) {
NativeMethods.CloseHandle(_SomeNativeHandle);
_SomeNativeHandle = IntPtr.Zero;
}
}
}
That's the simple version, but there are a lot of nuances that can trip you up on this pattern.
- The contract for IDisposable.Dispose indicates that it must be safe to call multiple times (calling Dispose on an object that was already disposed should do nothing)
- It can get very complicated to properly manage an inheritance hierarchy of disposable objects, especially if different layers introduce new Disposable and unmanaged resources. In the pattern above Dispose(bool) is virtual to allow it to be overridden so that it can be managed, but I find it to be error-prone.
In my opinion, it is much better to completely avoid having any types that directly contain both disposable references and native resources that may require finalization. SafeHandles provide a very clean way of doing this by encapsulating native resources into disposable that internally provide their own finalization (along with a number of other benefits like removing the window during P/Invoke where a native handle could be lost due to an asynchronous exception).
Simply defining a SafeHandle makes this Trivial:
private class SomeSafeHandle
: SafeHandleZeroOrMinusOneIsInvalid {
public SomeSafeHandle()
: base(true)
{ }
protected override bool ReleaseHandle()
{ return NativeMethods.CloseHandle(handle); }
}
Allows you to simplify the containing type to:
class SomeObject : IDisposable {
SomeSafeHandle _SomeSafeHandle;
FileStream _SomeFileStream;
// Something useful here
public virtual void Dispose() {
_SomeSafeHandle.Dispose();
_SomeFileStream.Dispose();
}
}
回答4:
I don't think so. You have control over when Dispose is called, which means you could in theory write disposal code that makes assumptions about (for instance) the existence of other objects. You have no control over when the finalizer is called, so it would be iffy to have the finalizer automatically call Dispose on your behalf.
EDIT: I went away and tested, just to make sure:
class Program
{
static void Main(string[] args)
{
Fred f = new Fred();
f = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Fred's gone, and he's not coming back...");
Console.ReadLine();
}
}
class Fred : IDisposable
{
~Fred()
{
Console.WriteLine("Being finalized");
}
void IDisposable.Dispose()
{
Console.WriteLine("Being Disposed");
}
}
回答5:
Not in the case you describe, But the GC will call the Finalizer for you, if you have one.
HOWEVER. The next garbage collection ,instead of being collected, the object will go into the finalization que, everything gets collected, then it's finalizer called. The next collection after that it will be freed.
Depending on the memory pressure of your app, you may not have a gc for that object generation for a while. So in the case of say, a file stream or a db connection, you may have to wait a while for the unmanaged resource to be freed in the finalizer call for a while, causing some issues.
回答6:
No, it's not called.
But this makes easy to don't forget to dispose your objects. Just use the using
keyword.
I did the following test for this:
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
foo = null;
Console.WriteLine("foo is null");
GC.Collect();
Console.WriteLine("GC Called");
Console.ReadLine();
}
}
class Foo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Disposed!");
}
回答7:
The GC will not call dispose. It may call your finalizer, but even this isn't guaranteed under all circumstances.
See this article for a discussion of the best way to handle this.
回答8:
The documentation on IDisposable gives a pretty clear and detailed explaination of the behavior, as well as example code. The GC will NOT call the Dispose()
method on the interface, but it will call the finalizer for your object.
回答9:
The IDisposable pattern was created primarily to be called by the developer, if you have an object that implements IDispose the developer should either implement the using
keyword around the context of the object or call the Dispose method directly.
The fail safe for the pattern is to implement the finalizer calling the Dispose() method. If you don't do that you may create some memory leaks i.e.: If you create some COM wrapper and never call the System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (which would be placed in the Dispose method).
There is no magic in the clr to call Dispose methods automatically other than tracking objects that contain finalizers and storing them in the Finalizer table by the GC and calling them when some clean up heuristics kick in by the GC.
来源:https://stackoverflow.com/questions/45036/will-the-garbage-collector-call-idisposable-dispose-for-me