C# Garbage collect and clean up
Garbage Collection
Memory has 2 types. Stack (that stores value types) and Heap (that stores
reference types). Value types are destroyed and their memory reclaimed when
they go out of scope. Reference types are created by the "new" key word, but are
not automatically destroyed.
The "new" operation allocates a chunk of raw memory and then converts this raw
memory to an object. The object can use multiple references and will only have
the memory reclaimed when all references have disappeared. To enable the
memory to be reclaimed, we need to use destructors.
Writing Destructors
A destructor is a speacial method which the runtime calls after the last reference
to an object has disappeared. The syntax for writing a destructor is a tilde (~)
followed by the name of the class.
Below is an example of a program that increments a static variable in a class when
a constructor is called and decriment the counter when the destructor is called.
class Tally
{
public Tally()
{
this.instanceCount++;
}
~Tally()
{
this.instanceCount--;
}
public static int InstanceCount()
{
return this.instanceCount;
}
...
private static int instanceCount = 0;
}
Internally, the C# compiler automatically translates a destructor into an override
of the Object.Finalize method. The compiler converts the following destructor:
class Tally
{
~Tally() { // your code goes here }
}
into this:
class Tally
{
protected override void Finalize()
{
try { // your code goes here }
finally { base.Finalize(); }
}
}
It’s important to understand that only the compiler can make this translation.
You can’t write your own method to override Finalize, and you can’t call Finalize
yourself
Disposal Methods
An example of a class that implements a disposal method is the TextReader class
from the System.IO namespace. This class provides a mechanism to read characters
from a sequential stream of input. The TextReader class contains a virtual method
named Close, which closes the stream. The StreamReader class (which reads
characters from a stream, such as an open file) and the StringReader class (which
reads characters from a string) both derive from TextReader, and both override the
Close method. Here’s an example that reads lines of text from a file by using the
StreamReader class and then displays them on the screen:
TextReader reader = new StreamReader(filename);
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
reader.Close();
The ReadLine method reads the next line of text from the stream into a string. The
ReadLine method returns null if there is nothing left in the stream. It’s important
to call Close when you have finished with reader to release the file handle and
associated resources. However, there is a problem with this example: it’s not
exception-safe. If the call to ReadLine or WriteLine throws an exception, the call
to Close will not happen; it will be bypassed. If this happens often enough, you
will run out of file handles and be unable to open any more files.
Disposal Solution
Although using the try..finally block would be a good solution at first glance,
the reference of the resource remains in scope after the finally block.
The best solution is to use the using statement.
When you are writing a new class, you should either create a destructor or
implement the IDisposable interface. Please see the Interface tab in this website
for an explanation on how to use IDisposable.