A common reason for defining a destructor is to provide insurance for the user forgetting to call the Dispose() method on an object which implements IDisposable, which would normally be done if the object uses unmanaged resources.
However, it can be used for any other clean-up purpose.
If an object does have a destructor then, when the garbage collector runs, it doesn't delete it straightway but puts it in the 'finalizer queue'. When the 'finalizer thread' is next run by the system, it removes objects from this queue and executes their destructors.
The object's heap memory is then reclaimed when the garbage collector next runs.
For more details on destructors, I'd check our recommended articles in the side-bar to the right of this thread.