I have written the very basic program below, I am new to C#. The destructor ~Program() doesn't get called, so I do not see in the output the 'Destructor called' string. I have checked other similar questions but I don't find the answer to mine. Thanks.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static System.Console;
namespace LyfeCicleObject
{
class Program
{
public Program()
{
WriteLine("Cons called");
}
~Program()
{
WriteLine("Destructor called");
}
static void Main(string[] args)
{
WriteLine("Main started");
Program p1 = new Program();
{
WriteLine("Block started");
Program p2 = new Program();
WriteLine("Block ended");
}
WriteLine("Main ended");
}
}
}
The short answer -- the reason you're not seeing the "Destructor called" output -- was buried somewhere in the comments:
(See: Finalizers (C# Programming Guide)).
.NET Framework will attempt to do it but .NET Core just won't do it.
Disclaimer: We have no way of knowing if these statements will continue to hold true; this is how they're implemented and documented as of now.
According to Raymond Chen, though, in his post Everybody thinks about garbage collection the wrong way, it would not be invalid if .NET Framework didn't run finalizers at the end of the program, either. The relevant quote, which says it from a different perspective, is this:
So as long as you don't assume that finalizers will run, it shouldn't matter how they're implemented or if an implementation changes.
Before going any further with C#, you're going to have to abandon the idea of destructors in .NET because they simply don't exist. C# uses C++'s destructor syntax for finalizers but the similarities stop there.
The good news is that there is a way to do something close to what you were trying to do, but it takes a paradigm shift, a substantial change in how you think about resource acquisition and release. Whether or not you really need to do it is a totally different question.
Finalizers aren't the only way, or even the best way, to release resources that need to be released in a timely manner. We have the disposable pattern to help with that.
The disposable pattern allows class implementors to opt in to a common mechanism for deterministically releasing resources (not including memory on the managed heap). It includes finalizers but only as a last chance at cleanup if the object wasn't disposed properly, especially if the process isn't terminating.
I'd say the main differences you'll see compared to C++ destructors are:
using
statement.What you won't see is that the memory won't necessarily be reclaimed immediately.
If you want to know more about how, read on...
Before I get into any code, it's worth mentioning some points of caution:
Dispose(bool)
after deriving from a class that implements the disposable pattern rather than creating the base of a class hierarchy that needs to be disposable. For example, the.Designer.cs
files in Windows Forms applications overrideDispose(bool)
in order to dispose of thecomponents
field if it's notnull
.Okay, code...
The following is an example of a simple class that implements the disposable pattern. It doesn't provide support for child classes so it's marked
sealed
andDispose(bool)
isprivate
.Actual cleanup takes place in the
Dispose(bool)
method. If the parameter istrue
, it means that disposal is happening via theIDisposable
interface (usually ausing
statement but not necessarily) and it's okay to clean up managed resources as well. If it'sfalse
, it means that disposal is happening as part of a GC sweep, so you can't touch managed resources because they may have already been collected.If you're writing a base class that needs to support the disposable pattern, things change slightly:
Dispose(bool)
is becomesprotected
andvirtual
so it can be overridden by subclasses but is still inaccessible to the consumer.The following is an example of a base class that supports the disposable pattern for subclasses.
And then the following is a subclass that uses that support. Subclasses don't also need to implement the finalizer or
IDisposable.Dispose
. All they have to do is overrideDispose(bool)
, dispose of their own resources, and then call the base implementation.So what does it mean to dispose managed and unmanaged resources?
Managed resources are things like other disposable objects and even non-disposable objects (e.g. strings). Some disposable types in the BCL will set such fields to
null
to ensure that the GC doesn't find active references to them.When your class is being disposed, the consumer has decided that it and its resources are no longer needed. If your object contains other disposables, it's okay to dispose those objects, and so on down the chain, because it's not happening during garbage collection.
Unmanaged resources are things like file handles, global memory, kernel objects... pretty much anything you allocated by making a call to the operating system. Those aren't affected by the garbage collector and need to be released no matter what, so they're not subject to the
disposing
test.If your disposable object used another disposable object that has an unmanaged resource, it's that object's responsibility to implement the Disposable pattern to release its resources, and your responsibility to use it.
Not all objects that implement
IDisposable
actually have unmanaged resources. Often a base class will support the disposable pattern simply because its author knows that at least one class that derives from it might need to use unmanaged resources. However, if a class doesn't implement the disposable pattern, one of its subclasses can introduce that support if it needs it.Let's alter your program a bit and make it do what you were expecting, but using the disposable pattern now.
Note: As far as I know, it's not very common to have
Main
create an instance of the containingProgram
class. I'm doing it here to keep things as close to the original as possible.Build and run against .NET Framework 4.7.2 and you get the following output:
Build and run against .NET Core 2.1 and you get the following output:
Instances
p1
andp2
were disposed in the reverse order in which they were constructed because of theusing
statements, and both managed and unmanaged resources were released. This was the desired behavior behind trying to use a "destructor".On the other hand, .NET Framework and .NET Core did things a bit different at the end, showing the differences I mentioned at the beginning:
p0
so it only released the unmanaged resources.p0
.If you want to see the output from finalizer:
If you want to know the reason of current behavior: the
p1
variable exists in the scope of theMain
method and will be collected by the GC when scope gets unreachable and I suppose it happens when program already stopped that means no GC (memory will be released in another way).