3.7 Creation and Destruction

only for RuBoard

The lifetime of an object running under .NET is not as straightforward as it is in other languages like C++ or managed VB.

In these languages, when an object is created, its constructor is called, which allows the object to be initialized . When the object goes out of scope, its destructor is called, providing a convenient place to free resources; this is usually the point at which database connections are closed, file handles are freed, and memory allocated during the object's lifetime is released. The object has a practical, convenient mechanism for cleanup. But as you will see, .NET handles things differently.

3.7.1 Constructors

VB.NET does provide the means to declare a constructor similar to that of Java, C++, and most other OO languages. In this respect, VB.NET is similar to these languages. However, in VB.NET, the declaration of a constructor is a little more intuitive. The constructor for an object is a method named New , which makes perfect sense because this is what is called when you declare a new instance of an object. The following fragment calls the default constructor for the Hello class:

 Dim hello As New Hello( )    'Calls Hello.New( ) 

The default constructor does not have any arguments, and, like all constructors, it does not return a value:

 Public Class Hello     Public Sub New( )         Console.WriteLine("Hello, World!")     End Sub End Class 

In addition to a default constructor, you can define New so it takes any number of arguments. In fact, you can define as many different versions of New as you want for the same object, as long as each has a different function signature. This process is called overloading, and it looks like this:

 Public Class New         Public Sub New( )         Console.WriteLine("Hello, World!")     End Sub         Public Sub New (name As String)         Console.WriteLine("Hello, {0}", name)     End Sub     End Class 
3.7.1.1 Instances versus references

Creating an instance of an object is different from declaring a reference to an object, which can be confusing to beginning programmers.

Object instances are created with the New operator, as in " gimme a new one of those." An instance of an object has methods that can be called and properties that can be accessed:

 Dim myLunch As New Donut( ) ''Creates an instance of Donut myLunch.Eat( )              'that can be used 

Object references, on the other hand, refer to other objects. By themselves , they do nothing. In fact, if you try to use an uninitialized reference, an exception will be thrown by the runtime:

 'No New here Dim lasso As Rope  'This is a reference to a Rope object lasso.Twirl( )     'Error, lasso does not refer to anything 

References must be assigned to an existing instance before they can be used. Think of an object reference as an alias:

 Dim chevy As New Car( )   'Create a new car Dim superSport As Car     'This is only a reference to the chevy     superSport = chevy        'Now superSport refers to chevy superSport.Drive( )       'Calls chevy.Drive( ) really 

In the previous fragment, there is only one object: chevy . The superSport reference is only an alias.

3.7.2 Destructors

VB.NET does not provide a destructor that is called when an object goes out of scope. Instead, the lifespan of a VB.NET object is nondeterministic . This means you don't know when an object will be freed. You can't rely on this to happen when the object goes out of scope (although it often does) because, like Java, memory is managed for you. The runtime provides a garbage collector that handles the memory associated with an object.

The runtime provides a way to release resources when objects are freed. It's called finalization . Because every object in .NET ultimately derives itself from System.Object , all objects inherit a method named Finalize . The implementation of this method, by default, is just an empty functionit doesn't do anything. However, it can be overridden.

If the Finalize method was overridden, when the garbage collector decides that our object is trash, it will be called. Then the memory used by the object will be reclaimed and made available once again. This process is similar to a traditional destructor, except Finalize is not called when the object goes out of scope. It's called after the object is no longer referenced anywhere in the program, and even then only when the garbage collector gets around to calling it.

Example 3-6 uses a class named Hello to display a message to the console. When the class constructor is called, the console displays "Hello, so and so!" When the garbage collector decides to free the object, Finalize is called, causing " Goodbye , so and so!" to be displayed in the console. Ignore the semantics of Finalize for now. It will become much clearer after you read about function overriding in the next chapter.

Example 3-6. Goodbye, World!
 'finalize.vb     Imports System     Public Class Hello       Private myName As String       Public Sub New(ByVal name As String)     myName = name     Console.WriteLine("Hello, {0}!", name)   End Sub       Protected Overrides Sub Finalize( )     Console.WriteLine("Goodbye, {0}!", myName)   End Sub     End Class     Public Class Application       Public Shared Sub Main( )     Dim hello As New hello("Mom")   End Sub     End Class 

Save this example to finalize.vb and compile it to a standard executable. When you run it, you see the following output:

 C:>goodbye Hello, Mom! Goodbye, Mom! 

This code appears to work just like a C++ program. You created an instance of Hello that caused "Hello, Mom!" to be displayed to the console. Hello goes out of scope and is garbage collected, and the Finalize method is called. "Goodbye, Mom!" is then displayed to the console.

This process is mostly correct. Finalize was not called because hello went out of scope, but because the application shut down. The only time when you can count on finalization to occur is when an application terminates, but only if the application terminates normally. Add the following line of code to the previous example. Place it right after the declaration of Hello :

 Public Shared Sub Main( )     Dim hello As New Hello("Mom")  Dim x As Integer = 31 / x  End Sub 

Running this code generates an unhandled overflow exception. If the CLR jumps in and asks if you would like to debug the executable, just say no. Control will return to the console window, and you will see that the object is never finalized (you won't see a goodbye message).

3.7.2.1 System.GC

Finalization can be forced using the System.GC class (for garbage collection), which provides limited access to the garbage collector. The class contains a method named GC.Collect that forces a garbage collection to occur when it is called. To better understand this, modify Main from the previous example to look like this:

 Public Shared Sub Main( )     Dim i As Integer     For i = 1 to 5         Dim hello As New Hello(i.ToString( ))     Next i End Sub 

The output should look like this:

 C:\>finalize Hello, 1! Hello, 2! Hello, 3! Hello, 4! Hello, 5! Goodbye, 5! Goodbye, 4! Goodbye, 3! Goodbye, 2! Goodbye, 1! 

The objects seem to be freed in the reverse order from which they were created, implying insertion into a stack-like structure. This is, in fact, what happens. Now, add a call to GC.Collect in Example 3-5:

 For i = 1 to 5     Dim hello As New Hello(i.ToString( ))  GC.Collect( )  Next i 

Now the objects are freed after each iteration of the loop:

 C:\>finalize Hello, 1! Goodbye, 1! Hello, 2! Goodbye, 2! Hello, 3! Goodbye, 3! Hello, 4! Goodbye, 4! Hello, 5! Goodbye, 5! 

When an object is created, if it contains a Finalize method, a pointer to the object is placed in the finalization queue . This pointer is an internal data structure maintained by the garbage collector that contains a list of objects that can be finalizedin other words, objects that implemented a Finalize method.

During garbage collection, this queue is traversed and all pointers found in it are placed in the freachable queue. Objects that do not have Finalize methods are freed at this point. Objects with a Finalize method are not (objects implementing Finalize are freed last).

A special runtime thread is dedicated to calling Finalize methods. When objects appear in the freachable queue, the thread is awakened, and Finalize is called for every object in the queue. Thus, just because you call GC.Collect doesn't mean that the object will be freed immediately. After all, the call occurs on a different thread. More often than not, you should avoid making calls to GC.Collect because of the overhead associated with the call; it's an application-wide call.

When a .NET process (think application domain) is initialized, the CLR allocates a contiguous block of memory called the managed heap. When objects are created, they are allocated from this heap until there is no more room left. When this happens (and when an application shuts down), a garbage collection is performed.

To make the garbage collector earn its money, modify Example 3-5 to create 10,000 instances of Hello :

 Dim i As Integer For i = 1 to 10000     Dim hello As New Hello(i.ToString( )) Next i 

The GC is optimized to work with small objects like Hello in its sleep, so add the following member variable:

 Public Class Hello  Private bigChunkOMemory(10000) As Byte  

Now when you run the example, you create 10,000 objects that are over 10,000 bytes in size . This should cause the GC to start pumping. Recompile the example and run it, but redirect the output to a text file instead of the console; otherwise the output won't fit:

 C:\>finalize > test.txt 

Run this code several times and examine the output each time. You should notice that the output is different almost every time you run the program. If there is one thing to learn from this example, it is that you should not count on objects being freed in the order they were created. They are freed in order in small examples that use a few very small objects. In real-world scenarios, though, objects can contain references to other objects, so the garbage collector cannot determine an order to perform finalization.

3.7.3 Close and Dispose

You should avoid using a Finalize method for several reasons. One reason is that objects with Finalize methods take longer to allocate. This book has shown how a pointer to the object must be stored in the finalization queue and later added to the freachable queue. This is no big deal in a simple program, but what about the example that allocates 10,000 objects? This scenario could add significant overhead, not to mention the overhead involving the garbage collector calling Finalize 10,000 times. In this situation, freeing an object takes longer as well. Also, objects that have a Finalize method are freed after everything else, so they could have a longer than necessary lifespan.

The best solution is to design your objects so they don't need cleaning up in the first place. But when it is necessary, you can solve this problem by implementing your own cleanup method. Then you can free resources explicitly whenever you want. The only foreseeable problem with implementing your own method is making sure that the people who will use your objects know about the method in the first place. However, some methods are specifically used for this purpose.

Classes in the .NET library follow the convention of using a Close or Dispose method. Typically, Close is used in situations when an object could be reopened at a later time; Dispose is used in situations when the object should not be used again. Example 3-7 shows a simple example that uses the Timer class. Before the program terminates, the timer is closed and its resources are freed.

Example 3-7. Close method
 'references: system.dll 'compile: vbc /t:exe /r:system.dll timer.vb     Option Strict On     Imports System Imports System.Timers     Public Class Application       Private Const EnterKey As Integer = 10       Public Shared Sub Main( )         'Create a Timer that fires every 2 seconds.     Dim myTimer As Timer = New Timer(2000)         'Specify Timer event             AddHandler myTimer.Elapsed, AddressOf OnTimerEvent         'Turn on the Timer     myTimer.Enabled = True         Console.WriteLine("Press ENTER to quit")         'Wait for ENTER key     Console.ReadLine( )         'Free resources associated with the Timer     myTimer.Close( )       End Sub       'The timer event   Public Shared Sub OnTimerEvent(ByVal source As Object, _     ByVal e As ElapsedEventArgs)     Console.WriteLine("Hello, World!")   End Sub     End Class 

As you can see in Example 3-7, the timer implements a Close method. If you examine the class in ILDASM (it's located in system.dll ), you won't be able to tell that the timer class also implements a Finalize method; it's inherited from System.ComponentModel.Component . If Close is called, Finalize is not called for the timer object and the overhead penalty of finalization is not incurred. If Close is not called (say, someone forgets to call it), the inherited Finalize method calls Close for you, so the resources allocated by the timer are eventually freed. Finalize is present only to act as a safety net.

only for RuBoard


Object-Oriented Programming with Visual Basic. Net
Object-Oriented Programming with Visual Basic .NET
ISBN: 0596001460
EAN: 2147483647
Year: 2001
Pages: 112
Authors: J.P. Hamilton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net