Chapter 3. Intermediate C#Reference Types
|
Reference Types
As discussed in Chapter 2, reference types
The particular reference type we'll
The class declaration in Listing 3.1 is simple and it compiles, but it's not useful. To make it useful, it should have a visibility modifier, such as
public
, if it was to be a library type,
Listing 3.1 A Basic Class Declaration ( BasicClass.cs )
class MyClass
{
// members
}
|
Object Construction/Destruction
The process of initializing and starting up a class is called
construction
. The
Another important aspect of object lifetime management is the destruction process. Chapter 1
Constructors
Constructors initialize the state of an object. A C# class can have two types of constructors: static and instance.
Static
constructors (also called type constructors) initialize the state of a type, which is available to all code that
Before looking at the syntax of static and instance constructors, it is important to understand the difference between static type access and instance access. Upon the first reference to a class, its type is loaded into memory. That type can have static members, which will be available to all code that accesses the type. In contrast, individual instances of a type can be instantiated and referenced as individual objects. Any members of a type that are not
Static ConstructorsA static constructor will initialize type state only one time, when the type is loaded. Although instance constructors have access to static data, they shouldn't ever touch static members. This would corrupt the type state every time an instance was created. Therefore, static constructors are the only solution to initialization of type state. Listing 3.2 shows how to create a static constructor. Listing 3.2 A Static Constructor ( StaticConstructor.cs )
using System;
public class MyStaticClass
{
public static int StaticInt;
static MyStaticClass()
{
StaticInt = 7;
}
}
class StaticConstructor
{
static void Main()
{
int myInt = MyStaticClass.StaticInt;
Console.WriteLine("myInt: {0}", myInt);
Console.ReadLine();
}
}
Points to pay attention to in the static constructor in Listing 3.2 are that it begins with the
static
modifier, does not specify a return type, and the
Instance ConstructorsThe state of each individual instance should be initialized in instance constructors. Listing 3.3 shows how to create an instance constructor. Listing 3.3 An Instance Constructor ( InstanceConstructor.cs )
using System;
public class MyInstanceClass
{
public int MyInstanceInt;
public MyInstanceClass()
{
MyInstanceInt = 7;
}
}
class InstanceConstructor
{
static void Main()
{
MyInstanceClass myClass = new MyInstanceClass();
int myInt = myClass.MyInstanceInt;
Console.WriteLine("myInt: {0}", myInt);
Console.ReadLine();
}
}
Similar to the static constructor, an instance constructor does not return a value and has the same name as its enclosing type. The syntax differences are that there is no static modifier, and instance constructors can accept parameters and be overloaded (different types and number of parameters). Instance constructors also have visibility modifiers (discussed in a later section). Notice in the Main method that a new instance of MyInstanceClass is being created with the new operator. This is what distinguishes usage of instance from static types, where each individual instance must be explicitly created, but static types are referenced by specifying the type and the static member to access. DestructorsA C# destructor is a special type member that is invoked when garbage collection occurs and the instance it is a member of is available for collection. Instances become available for collection when there are no more references to them. For example, the instance could go out of scope or be explicitly set to null . The purpose of the destructor is to release unmanaged resources, such as file streams, network connections, or database connections, to name a few. If there are no unmanaged resources to release, a destructor is not necessary. Because destructors slow down memory management and don't guarantee proper destruction of unmanaged resources, additional measures are necessary to properly manage release of unmanaged resources.
During the garbage collection (GC) process, types with a destructor have to be visited twice. The first pass of the garbage collector (GC)
The GC is nondeterministic and can't guarantee when or if a C# destructor will be called. The GC happens when memory pressure forces it into operation. This is by design because just-in-time garbage collection is more efficient in the general case. If a program has a small footprint, it is possible that a GC will never happen at all. Subsequently, multiple unmanaged resources may be held within the type and never released for the life of the program. If the program crashes, destructors will not be called. One would hope that the OS would help recover some of its resources, but this may not occur and such cleanup behavior is not
The proper way to release unmanaged resources in C# is to implement the dispose pattern in a type. The IDisposable interface (discussed in the "Interfaces" section of Chapter 4) helps implement the dispose pattern. Listing 3.4 describes how to implement a destructor. Listing 3.4 Destructor Implementation ( Destructor.cs )
using System;
public class MyInstanceClass
{
// some implementation
~MyInstanceClass()
{
Console.WriteLine("Releasing Resources...");
}
}
class Destructor
{
static void Main()
{
MyInstanceClass myClass = new MyInstanceClass();
myClass = null;
GC.Collect();
Console.ReadLine();
}
}
The
myClass
reference in the
Main
method of Listing 3.4 is set to
null
right after it is instantiated. Because there aren't any other references to this instance, setting it to
null
makes it available for garbage collection. Objects go out of scope and are available for garbage collection if they are allocated in a routine and there are no other references to them. However, while inside a routine, objects
Remember that garbage collection occurs in a nondeterministic manner; the destructor can be invoked some time in the future. Syntax for destructors includes the tilde ( ~ ) followed by the name of the destructor, which is the same as its enclosing type. Structs do not have destructors, so it is best to ensure that the disposable pattern, described in the "Interfaces" section of Chapter 4, is implemented.
The
Main
method of Listing 3.4 calls
GC.Collect
to force a garbage collection. You shouldn't call
GC.Collect
yourself unless you are
Now consider the case where the call to GC.Collect was deleted or commented out. Because this program is so small, there will never be any memory pressure or other implicit event to force the garbage collection, meaning that the destructor will never be executed until the program exits. You can start this program and go get a cup of coffee, go out for dinner, or even go on vacation and that destructor will not be called. The section on interfaces in Chapter 4 will show how to fix this problem. |