<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" AutoEventWireup="false" Inherits="TSPage" title="Doing Objects in VB.NET and C# - CONSTRUCTION AND DEMOLITION" MetaDescription="An online e-book and fast-track tutorial for experienced developers. Written by Little Rock, Arkansas-based software consultant, Terry Smith." LocalStylesheet="stylesheet.css" %> Previous ] [ Index ] [ Next ]   

2

Construction and Demolition

Isn't software programming great? Not only do we get to build things, but we also get to break and demolish them as well. Granted, we typically break things by accident, but nevertheless it's fun and we can always write it up as "great exercise". Therefore, this chapter starts with constructors and creating objects and then concludes with how to destroy them.

Object Construction

Let's attempt to cover constructors without boring ourselves. We'll start by looking at normal constructors, then make things more interesting by taking a look at inheritance and constructors, and then conclude with static constructors.

Instance Constructors

Constructors in VB.NET have the name New, and constructors in C# have the same name as the type, just as they do in C++ or Java. I say "type" because structures can have constructors as well (though they cannot have default, parameterless constructors as was mentioned previously). Constructors can be overloaded in both VB.NET and C#. The following is a VB.NET class with two constructors:

Public Class AnsweringMachine
   Public Sub New()
      Console.WriteLine("Default constructor called.")
      Console.WriteLine("Leave a message after the beep.")
      Microsoft.VisualBasic.Beep()
   End Sub

   Public Sub New(ByVal message As String)
      Me.New()
      Console.WriteLine("Received message: " + message)
   End Sub
End Class

The single-argument constructor calls the default constructor, which would not be called by the VB.NET compiler otherwise. Here is almost the same example using a C# class:

public class AnsweringMachine
{
   public AnsweringMachine()
   {
      Console.WriteLine("Default constructor called.");
      Console.WriteLine("Leave a message after the silence.");
   }

   public AnsweringMachine(string message) : this()
   {
      Console.WriteLine("Received message: " + message);
   }
}

First, if you were paying attention then you probably are wondering where the Beep went to. It's part of the Visual Basic .NET Runtime and not available in C#. So if you're building a component for R2D2, consider using VB.NET. Next, you'll notice the this() initializer used by the single-argument constructor in order to call the default constructor. When using this(), the default constructor will be invoked before the body of the single-argument constructor. You can also pass arguments within the initializer in order to call a non-default constructor.

Inheritance and Constructors

Chapter 5 will cover inheritance in more detail, but there are some inheritance topics involving constructors and destructors that we are going to cover in this and in the next section. Let's add a parent class to our VB.NET example as follows:

Public Class MotherOfAllMachines
   Public Sub New()
      Console.WriteLine("Mama constructor called.")
   End Sub
End Class

Public Class AnsweringMachine
   Inherits MotherOfAllMachines

   Public Sub New()
      Console.WriteLine("Default constructor called.")
      Console.WriteLine("Leave a message after the beep.")
      Microsoft.VisualBasic.Interaction.Beep()
   End Sub

   Public Sub New(ByVal message As String)
      Me.New()
      Console.WriteLine("Received message: " + message)
   End Sub
End Class

If an AnsweringMachine instance is created using the following:

Dim machine As New AnsweringMachine("Constructor construction junction")

Then the parent's constructor will be invoked automatically first, followed by the AnsweringMachine's default constructor, and then the single-argument constructor. The output resembles the following:

Mama constructor called.
Default constructor called.
Leave a message after the beep.
Received message: Constructor construction junction

The parent's constructor is automatically called only if there is a default constructor. If a default constructor is not explicitly defined, the compiler will generate one for you (as is the case in C++ and Java). A default constructor will not be generated if a base class implements a non-default constructor. In such a case, a derived class must explicitly call its parent's constructor, supplying the appropriate arguments, in order for the initialization of the object to proceed. In VB.NET this is done using MyBase.New(...) in the first line of the child's non-default constructor as shown below:

Public Class MotherOfAllMachines
   Public Sub New(ByVal parameter As Integer)
      Console.WriteLine("Mama constructor called.")
   End Sub
End Class

Public Class AnsweringMachine
   Inherits MotherOfAllMachines

   Public Sub New()
      MyBase.New(42)
      Console.WriteLine("Default constructor called.")
      Console.WriteLine("Leave a message after the beep.")
      Microsoft.VisualBasic.Interaction.Beep()
   End Sub

   Public Sub New(ByVal message As String)
      MyBase.New(42)
      Console.WriteLine("Received message: " + message)
   End Sub
End Class

The code above would not compile if both of the AnsweringMachine's constructors did not explicitly call the non-default constructor of their parent class. Also notice that the Me.New() call is now missing from AnsweringMachine's non-default constructor. This is an interesting battle. Both MyBase.New and Me.New must be the very first line within a constructor, so you have a choice between either one or the other. Realistically, you would call MyBase.New, which is required in a case such as that above, and move any other shared construction logic into a separate method callable by all constructors within the class.

All of the above, in principle, is the same in C#. If a parameterless base class constructor exists, it will be called for you. Otherwise, you need to specify a base(...) initializer similarly to how the this() initializer was used in our previous C# example. Here is the C# equivalent of the VB.NET code above:

public class MotherOfAllMachines
{
   public MotherOfAllMachines(int parameter)
   {
      Console.WriteLine("Mama constructor called.");
   }
}

public class AnsweringMachine : MotherOfAllMachines
{
   public AnsweringMachine() : base(42)
   {
      Console.WriteLine("Default constructor called.");
      Console.WriteLine("Leave a message after the silence.");
   }

   public AnsweringMachine(string message) : base(42)
   {
      Console.WriteLine("Received message: " + message);
   }
}

Static Constructors

If you are a refugee from Java (or perhaps you now consider yourself a double-agent) you are likely familiar with static initializers. Static initializers (which also go by the following aliases: type constructors, static constructors, or type initializers) are used to initialize static variables within a class.

Now, let's play a game. All of the following rules apply to static initializers except one. Which one is it?

Unlike all the other major programming languages, VB.NET uses the keyword Shared instead of static, but don't let that throw you. It's the same thing. Shared constructors within VB.NET have implicit Public access whereas static constructors in C# have implicit private access. Not that it matters being that the compiler calls all static constructors for you. In fact, you cannot explicitly specify any access level for static constructors in either VB.NET or C#.

Static constructors are used to initialize static member variables. They are also sometimes used to write or initialize log files as shown in this shocking example:

Public Class StaticElectricity
   Shared Sub New()
      Debug.WriteLine("StaticElectricity class has been loaded and charged.")
   End Sub
End Class

If the above class had a static member variable, you might decide to initialize it inline as follows:

Public Class StaticElectricity
   Private Shared mCharge As Double = 545.32

   Shared Sub New()
      Debug.WriteLine("StaticElectricity class has been loaded and charged.")
   End Sub
End Class

When the compiler works its magic on this code example, it will move the initialization of m_Charge from its inline initialization to the very top of the Shared Sub New() initializer. If you have not defined a static initializer for a class, the compiler generates one for you and moves the intermediate language for the initialization of any static members into it. The same applies to C#. Here is a similar example that illustrates the C# syntax:

public class StaticElectricity
{
   private static double mCharge;

   static StaticElectricity()
   {
      mCharge = ScootAroundRoomOnCarpetWithSocks();
      Debug.WriteLine("StaticElectricity class has been loaded and charged.");
   }

   private static double ScootAroundRoomOnCarpetWithSocks()
   {
      // Do real-world logic here.
      return 545.32;
   }
}

Birthing Objects with VB.NET

Further above, when we were discussing how to code an answering machine, you might have took note of the following syntax, especially if you have a lot of experience with Visual Basic 6.0:

Dim machine As New AnsweringMachine("Constructor construction junction")

This "inline" method of creating objects was actively discouraged in VB 6.0 via punishment enforced by members of the Code Review Gestapo (with echo). The reason being that the keywords As New in VB 6.0 actually initialized an object to Nothing. Then, every time the object variable was encountered it was first evaluated before the code executed. If the object was Nothing, the object was created before the code that used it executed. In other words, As New was Ass Stupid. Instead, developers were encouraged to declare an object separately from its instantiation and to explicitly instantiate it immediately before it is used. Thankfully, there is no implicit object creation in VB.NET and objects are explicitly created using As New. In addition, all constructors in VB 6.0 had to be parameterless and that is no longer a limitation.

Object Demolition

In case you haven't yet discovered why Visual Studio .NET licenses are so expensive, it's because Microsoft is throwing in lifetime garbage collection for all your code. And we both know that's a lot of garbage! Since this e-Book is not written for total newbies, I assume that you're already aware that in a garbage collected language the memory for objects is automatically reclaimed once your application is finished with it. Garbage collection in .NET, as it is in Java, is non-deterministic, meaning that Bill Gates will clean up after you whenever he sees fit.

Since garbage collection is non-deterministic, you have no control over when a class's destructor (C#) or finalizer (VB.NET) is executed or whether it even runs at all. You will see both the destructor and finalizer terminology used interchangeably in .NET literature; however, finalization and finalizer are technically more accurate terms, being that finalization occurs after an object has been destructed as far as your code is concerned. The VB.NET and C# compilers both emit Finalize methods into the Intermediate Language code. C# does so by looking for a finalizer written using the C++ destructor syntax, an arguable and possibly confusing choice, as demonstrated in this simple example:

public class BornToDie
{
   public static void Main()
   {
      BornToDie deadCode = new BornToDie();
   }

   ~BornToDie()
   {
      System.Diagnostics.Trace.WriteLine("I have been finalized. Finally!");
   }
}

The example above creates an instance of BornToDie and then simply exists. The garbage collector is invoked as the application exists (though there is no guarantee of this) and BornToDie's finalizer is called. Here is the same example in VB.NET, notice the Finalize syntax:

Public Class BornToDie
   Public Shared Sub Main()
      Dim deadCode As New BornToDie()
   End Sub

   Protected Overrides Sub Finalize()
      System.Diagnostics.Trace.WriteLine("I have been finalized. Finally!")
   End Sub
End Class

For now, take note of the Overrides keyword used on the Finalize method declaration. We will cover this later in Chapter 6 on inheritance.

Let me reiterate that there are no guarantees concerning when a finalizer will execute. If you have a class that includes member classes that in turn hold pointers to other member classes, and all of these classes implement finalizers, the finalizers implemented by these classes could be called in any order. (It's just like after a snowstorm during the holiday season, the garbage collector may run or he may not. When he does, who knows which neighborhood will be first?) Therefore, within a finalizer be very careful about accessing inner, member objects, and in fact, try to avoid doing so completely. Finalizers are intended to free unmanaged resources, not other managed objects which can implement their own finalizers.

Defensive Demolition Disassembly

Microsoft .NET includes a large assortment of tools and utilities, most of which are invoked from the command-line, and a few of which are even useful. I strongly encourage you to lookup the phrase ". NET Framework Tools" in the .NET Framework SDK Documentation to view a list of these. Among the tools is the Intermediate Language Disassembler (ILDasm.exe). The IL Disassembler displays the IL of a DLL or EXE in human-readable format (or more correctly, nerd-readable format) either through its GUI or by outputting a text file that can be sent to its counterpart, the IL Assembler. Note that unless an assembly has already been converted to a native image (by using the Native Image Generator Tool, ngen.exe), it is already in IL form. The IL Disassembler simply formats the IL into a readable form. It's technically not "disassembling" anything.

If you have been reading along so far without typing in any of the examples, now is a perfect time to get more involved. Create a Console Application in either VB.NET or C# (or both!) using the New Project wizard and type in one of the tiny BornToDie code examples further above. Test the code to your satisfaction first. Then launch the Visual Studio .NET Command Prompt in your Programs menu beneath Microsoft Visual Studio .NET\Visual Studio .NET Tools. Run ildasm.exe and browse to your EXE. You should see something similar to the window below. This is displayed after browsing to the VB.NET example:

By double-clicking on the Finalize method we can view the IL for the method, which will be displayed in a separate window like that below:

This is not a tutorial on intermediate language. I believe the code above is fairly self-explanatory. Now, let's examine the IL emitted for the C# example. Remember that the logic of the two BornToDie examples are exactly identical. The following is displayed after browsing to the C# EXE and double-clicking its finalizer:

C# has generated into the IL try and finally blocks for us as well as a call to our parent class's Finalize method within the finally block! How cool, yet totally inconsistent! In fact, in C# if you attempt to explicitly call your base class's Finalize method from your destructor, you will get a compiler error informing you that it is called for you automatically.

Both of these steps are extremely important. They ensure that the parent class's finalizer is always called. Unfortunately, a brief look back at the IL generated by the VB.NET compiler shows that the same is not true of VB.NET. So you may be thinking, "I just need to be sure to always write the same try/finally logic myself in my VB.NET classes, right?" Unfortunately, you can't because you cannot call your base class's finalizer in VB.NET.

If you're beginning to think that finalizers are more trouble than they're worth, then you're getting the hang of it. (Though I still prefer garbage collection to the reference counting nightmares of COM.) Here's "da rule": If your class uses an unmanaged resource (i.e. you have a Win32 handle to a file, socket, or something similar attained via an API call), then implement a finalizer to guarantee that it is cleaned up appropriately. However, whenever possible use the .NET Framework classes for files, sockets, etc. because they manage resources for you and implement their own finalizers. Then, you will only need to call a Close or Dispose method on the object, which brings us to our next topic. Use finalizers only as a last resort.

I Am Disposable

In many cases you need resources held by your objects to be cleaned up when you want them to be, not when the stinky garbage collector wants them to be. Luckily, there is a buzzword design pattern in .NET for performing just such a thing. It's the use of the IDisposable interface to implement the dispose pattern. The IDisposable interface looks like this:

public interface IDisposable
{
   public void Dispose();
}

Why is this a design pattern? The interface itself is not a design patter per se; it's the common use of the interface throughout many of the .NET Framework classes offering explicit cleanup of resources. You will also see the Close method used often. It is part of the design pattern as well, and often the Close method simply calls Dispose. For instance, it seems more natural to "close" a file rather than "dispose of" it. The SqlConnection class implements both methods.

Chapter 5 will cover implementing interfaces in more detail, but let's skip ahead and take a look at implementing the IDisposable interface. The following is a simple example of how you might do so in VB.NET for a class that manages a FileStream:

Imports System.IO

Public Class UseMeAndDisposeMe
   Implements IDisposable

   Private mFileStream As FileStream

   Public Sub Open()
      ' Note: This can throw 8 different kinds of
      ' exceptions, but that's beyond the scope of this example.
      mFileStream = New FileStream("temp.txt", FileMode.Create)
   End Sub

   ' ... methods that do something with the FileStream would be here ...

   Public Sub Dispose() Implements IDisposable.Dispose
      If Not mFileStream Is Nothing Then
         mFileStream.Flush()
         mFileStream.Close()
         mFileStream = Nothing
      End If
   End Sub
End Class

This class does not implement a finalizer; however, if it did you could call the Dispose method from the finalizer "just to be safe". Then, you would want to call GC.SuppressFinalize(this) within the Dispose method. This prevents finalization from occurring since the object's resources have already been cleaned up. When garbage collection occurs, the object's memory will simply be reclaimed without the finalizer being executed. In instances where you have a large number of objects, perhaps a few hundred or thousand in an array, suppressing finalization can be a significant performance boost.

Here is the C# equivalent to the VB.NET code above:

using System;
using System.IO;

public class UseMeAndDisposeMe : IDisposable
{
   private FileStream mFileStream;

   public void Open()
   {
      // Note: This can throw 8 different kinds of
      // exceptions, but that's beyond the scope of this example.
      mFileStream = new FileStream("temp.txt", FileMode.Create);
   }

   // ... methods that do something with the FileStream would be here ...

   public void Dispose()
   {
      if (mFileStream != null)
      {
         mFileStream.Flush();
         mFileStream.Close();
         mFileStream = null;
      }
   }
}

If you were creating, using, and closing a FileStream within a single method, then the logic would look like this:

public void StreamAByte()
{
   FileStream fileStream = null;

   try
   {
      fileStream = new FileStream("temp.txt", FileMode.Create);
      fileStream.WriteByte( (byte)42  );
      fileStream.Flush();
   }
   finally
   {
      if (fileStream != null)
      {
         ((IDisposable)fileStream).Dispose();
      }
   }
}

With C# you can reduce the method above down to the following by using the using statement:

public void StreamAByte()
{
   using (FileStream fileStream = new FileStream("temp.txt", FileMode.Create))
   {
      fileStream.WriteByte( (byte)42  );
      fileStream.Flush();
   }
}

The using statement generates the try and finally blocks along with a call to Dispose in the finally block. Variables specified between using's parenthesis must implement IDisposable and must be initialized within the parenthesis as well. You can initialize multiple variables by separating them with commas.

Unfortunately, the using statement is not available in VB.NET. In this particular example, there is another problem as well. Opening and writing to files can generate any of a number of exceptions. You cannot attach catch statements to the end of the using statement and embedding the using block within another try/catch would defeat its purpose. So, in this particular instance, I would not use the shortcut method and opt for the long form instead by implementing try/catch/finally exception handling.

Previous ] [ Index ] [ Next ]   

Amazon Honor System Click Here to Pay Learn More