The Life and Times of an Object
First, let's recap what happens when you create and destroy an object.
You create an object like this:
TextBox message = new TextBox(); // TextBox is a reference type
From your point of view, the new operation is atomic, but underneath, object creation is
really a two-phase process. First, the new operation has to allocate some raw memory
from the heap. You have no control over this phase of an object's creation. Second, the
new operation has to convert the raw memory into an object; it has to initialize the object.
You can control this phase this by using a constructor.
NOTE
C++ programmers should note that in C#, you cannot overload new to control allocation.
When you have created an object, you can then access its members by using the dot
operator. For example:
message.Text = "People of Earth, your attention please";
You can make other reference variables refer to the same object:
TextBox ref = message;
How many references can you create to an object? As many as you want! The runtime
has to keep track of all these references. If the variable message disappears (by going out
of scope), other variables (such as ref) might still exist. Therefore, the lifetime of an
object cannot be tied to a particular reference variable. An object can only be destroyed
when all the references to it have disappeared.
NOTE
C++ programmers should note that C# does not have a delete operator. The runtime
controls when an object is destroyed.
Like object creation, object destruction is also a two-phase process. The two phases of
destruction exactly mirror the two phases of creation. First, you have to perform some
tidying up. You do this by writing a destructor. Second, the raw memory has to be given
back to the heap; the memory that the object lived in has to be deallocated. Once again,
you have no control over this phase. The process of destroying an object and returning
•
struct Tally
•
{
•
~Tally() { ... } // compile-time error
}
•
You cannot declare an access modifier (such as public) for a destructor. This is
because you never call the destructor yourself—the garbage collector does.
public ~Tally() { ... } // compile-time error
•
You never declare a destructor with parameters, and it cannot take any parameters.
Again, this is because you never call the destructor yourself.
~Tally(int parameter) { ... } // compile-time error
•
The compiler automatically translates a destructor into an override of the
Object.Finalize method. The compiler translates the following destructor:
•
class Tally
•
{
•
~Tally() { ... }
}
Into this:
class Tally
{
protected override void Finalize()
{
security high on its list of design goals. Instead, the garbage collector is responsible for
destroying objects for you. The garbage collector guarantees the following:
•
Each object will be destroyed and its destructors run. When a program ends, all
oustanding objects will be destroyed.
•
Each object is destroyed exactly once.
•
Each object is destroyed only when it becomes unreachable; that is, when no
references refer to the object.
These guarantees are tremendously useful and free you, the programmer, from tedious
housekeeping chores that are easy to get wrong. They allow you to concentrate on the
logic of the program itself and be more productive.
When does garbage collection occur? This might seem like a strange question. After all,
surely garbage collection occurs when an object is no longer needed. Well, it does, but
not necessarily immediately. Garbage collection can be an expensive process, so the
runtime collects garbage only when it needs to (when it thinks available memory is
starting to run low), and then it collects as much as it can. Performing a few large sweeps
of memory is more efficient than performing lots of little dustings!
NOTE
You can invoke the garbage collector in a program by calling the static method
System.GC.Collect(). However, except in a few cases, this is not recommended. The
System.GC.Collect method starts the garbage collector, but the process runs
asynchronously and when the method call finishes you still don't know whether your
objects have been destroyed. Let the runtime decide when it is best to collect garbage!
One feature of the garbage collector is that you don't know, and should not rely upon, the
order in which objects will be destroyed. The final point to understand is arguably the
most important: Destructors do not run until objects are garbage collected. If you write a
destructor, you know it will be executed, you just don't know when.
How Does the Garbage Collector Work?
guaranteed. Therefore, ensure that destructors do not depend on each other, or overlap
with each other (don't have two destructors that try to release the same resource, for
example).