DekGenius.com
[ Team LiB ] Previous Section Next Section

3.2 Core Features and Languages

Since one of .NET's goals is to support a common paradigm for application programming, it must specify and utilize programming concepts consistently. In this section, we will examine four core Microsoft .NET languages, including Managed C++, VB.NET, C#, and J#, and several core programming concepts that all .NET languages support, including:

Namespace

Mitigates name collisions.

Interface

Specifies the methods and properties that must be implemented by objects that expose the interface.

Encapsulation

In object-oriented languages, allows a class to combine all its data and behavior.

Inheritance

Allows a class to inherit from a parent class so that it can reuse rich functionality that the parent class has implemented, thus reducing development effort and programming errors.

Polymorphism

Permits developers to specify or implement behaviors in a base class that can be overridden by a derived class. This is a very powerful feature because it allows developers to select the correct behavior based on the referenced runtime object.

Exception handling

Allows us to write easier-to-understand code because it allows us to capture all errors in a common, understandable pattern—totally opposite to that of nine levels of nested conditional blocks.

Although this is not a complete list of concepts that .NET supports, it includes all the major .NET concepts that we want to cover in this section. We will show you examples of all these features in Managed C++, VB.NET, C#, and J#. These concepts are nothing new: we're merely demonstrating how they're represented in all core Microsoft .NET languages.

Before we start, you should understand first what our examples will accomplish. First, we will create a namespace, called Lang, that encapsulates an interface, ISteering. Then we will create two classes: Vehicle, which is an abstract base class that implements ISteering, and Car, which is a derivative of Vehicle. We will support an entry point that instantiates and uses Car within a try block. We will unveil other details as we work through the examples.

3.2.1 Managed C++ Code

Managed C++ is Microsoft's implementation of the C++ programming language with some newly added keywords and features to support .NET programming. This allows you to use C++ to develop managed objects, which are objects that run in the CLR. Using Managed C++, you can obtain the performance[1] that is inherent in C++ programs, and at the same time, you can also take advantage of CLR features.[2]

[1] You can easily mix managed and unmanaged code in C++ programs. The unmanaged code will perform better. See this chapter's example code, which you can download from http://www.oreilly.com/catalog/dotnetfrmess3/.

[2] However, if you look carefully at the features and new keywords (_ _abstract, _ _box, _ _delegate, _ _gc, _ _nogc, _ _pin, etc.) that have been added to Microsoft C++, we doubt that you'll want to use Managed C++ to write new code for the CLR, especially when you have C#.

Now let's look at an example that includes all the concepts we want to examine. As you can see in the following code listing, we start off creating a new namespace, Lang, which envelops everything except main( ). With the exception of the first line and special keywords, the code listing conforms perfectly to the C++ standard:

#using <mscorlib.dll>
using namespace System;

namespace Lang
{

Next, we specify an interface, called ISteering. If you are a C++ programmer, you will immediately notice that there are two new keywords in the following code listing, _ _gc and _ _interface. The new keyword _ _interface allows you to declare an interface, which is basically equivalent to an abstract base class in C++. In other words, the two method prototypes are specified, but not implemented here. The class that implements this interface provides the implementation for these methods:

_  _gc _  _interface ISteering
{
  void TurnLeft(  );
  void TurnRight(  );
};

If you are a COM programmer, you know that in COM you have to manage the lifetimes of your objects and components yourself. Even worse, you also have to rely on your clients to negotiate and interoperate correctly with your COM components; otherwise, extant references will never be reclaimed. Managed C++ removes this problem by adding a new keyword, _ _gc that tells the CLR to garbage-collect the references to your interface when they are no longer in use. Aside from these two keywords, the previous code listing requires no other explanation for programmers who have experience with C-like languages.

Now that we have an interface, let's implement it. The following code listing is a Managed C++ class (as indicated by the _ _gc) that implements our ISteering interface. One thing to notice is that this class is an abstract base class because the ApplyBrakes( ) method is a pure virtual function, as indicated by the =0 syntax. Vehicle doesn't provide the implementation for this method, but its derived class must supply the implementation:

_  _gc class Vehicle : public ISteering  
{
  public:

  void TurnLeft(  )
  {
    Console::WriteLine("Vehicle turns left."); 
  }

  void TurnRight(  )
  {
    Console::WriteLine("Vehicle turns right."); 
  }

  virtual void ApplyBrakes(  ) = 0;
};

Since Vehicle is an abstract base class and can't be instantiated, we need to provide a Vehicle derivative, which we will call Car. As you can see in the following listing, everything about the class is C++, with the exception of the keyword _ _gc. Note that the ApplyBrakes( ) function first dumps a text message to the console and then immediately creates and throws an exception, notifying an exception handler that there has been a brake failure.

_  _gc class Car : public Vehicle 
{
  public:

  void ApplyBrakes(  )
  {
    Console::WriteLine("Car trying to stop.");

    throw new Exception ("Brake failure!");
  }
};

} // This brace ends the Lang namespace.

What is special here is that the Exception class is a part of the .NET Framework, specifically belonging to the System namespace. This is great because this class works exactly the same way in all languages and there's no longer a need to invent your own exception hierarchy.

Two important types that derive from Exception are SystemException (framework and Windows errors) and ApplicationException (your application's errors). If you create your own exception classes, they should derive from ApplicationException. .NET conventions also suggest throwing ApplicationException objects for application-specific errors.

Now that we have a concrete class, we can write the main( ) function to test our Car class. Notice that we have added a try block that encapsulates the bulk of our code so that we can handle any exceptions in the catch block. Looking carefully at the following code listing, you'll see that we've instantiated a new Car on the managed heap, but we've actually referred to this Car instance using a Vehicle pointer. Next, we tell the vehicle to TurnLeft( )—there's no surprise here because we've implemented this method in Vehicle. However, in the following statement, we tell the Vehicle that we're applying the brakes, but ApplyBrakes( ) is not implemented in Vehicle. Since this is a virtual method, the correct vptr and vtbl[3] will be used, resulting in a call to Car::ApplyBrakes( ). Of course Car::ApplyBrakes( ) will throw an exception, putting us into the catch block. Inside the catch block, we convert the caught exception into a string and dump it out to the console.

[3] Many C++ compilers use vtbls (a vtbl is a table of function pointers) and vptrs (a vptr is a pointer to the vtbl) to support dynamic binding or polymorphism.

We can do this because Exception is a class in the .NET Framework and all classes in the framework must derive from System.Object, which implements a rudimentary ToString( ) function to convert any object into a string:

void main(  )
{
  try 
  {
    Lang::Vehicle *pV = 0;   // Namespace qualifier
    pV = new Lang::Car(  );      // pV refers to a car
    pV->TurnLeft(  );           // Interface usage
    pV->ApplyBrakes(  );         // Polymorphism in action
  }
  catch(Exception *pe)
  {
    Console::WriteLine(pe->ToString(  ));
  }
}

Notice that you don't have to deallocate your objects on the managed heap when you've finished using them, because the garbage collector will do that for you in .NET.

Although this is a simple example, we have used Managed C++ to illustrate all major object-oriented programming concepts, including namespaces, interfaces, encapsulation, inheritance, polymorphism, and exception handling. Next, we demonstrate that you can translate this code into any other .NET language because they all support these concepts. Specifically, we'll show you this same example in VB.NET, C#, J#, and IL, just to prove that these concepts can be represented the same way in all languages that targets the CLR.

3.2.2 VB.NET Code

Microsoft has revamped VB and added full features for object-oriented programming. The new VB language, Visual Basic .NET (or VB.NET), allows you to do all that you can do with VB, albeit much more easily. If you are a VB programmer with knowledge of other object-oriented languages, such as C++ or Smalltalk, then you will love the new syntax that comes along with VB.NET. If you are a VB programmer without knowledge of other object-oriented languages, you will be surprised by the new VB.NET syntax at first, but you will realize that the new syntax simplifies your life as a programmer.[4]

[4] To learn more about VB.NET, see O'Reilly's VB.NET Language in a Nutshell, Second Edition, by Steven Roman, PhD., Ron Petrusha, and Paul Lomax, or Programming Visual Basic .NET, Second Edition, by Jesse Liberty.

In addition to the VB-style Rapid Application Development (RAD) support, VB.NET is a modernized language that gives you full access to the .NET Framework. The VB.NET compiler generates metadata and IL code, making the language an equal citizen to that of C# or Managed C++. Unlike VB versions prior to VB6, there will be no interpreter in VB.NET, so there should be no violent arguments about performance drawbacks of VB versus another language.

Perhaps the most potent feature is that now you can write interfaces and classes that look very similar to those written in other .NET languages. The new syntax allows you to inherit from base classes, implement interfaces, override virtual functions, create an abstract base class, and so forth. In addition, it also supports exception handling exactly as does C# and Managed C++, making error handling much easier. Finally, VB.NET ships with a command-line compiler, vbc.exe, introduced in Chapter 2.

Let's see how to translate the previous Managed C++ program into VB.NET so that you can see the striking conceptual resemblance. First, we'll start by defining a namespace called Lang, shown here in bold:

Imports System

Namespace Lang

Next, we specify the ISteering interface, which is easy to do in VB.NET since the syntax is very straightforward, especially when you compare it with Managed C++. In the following code listing, you'll notice that instead of using opening and closing braces as in Managed C++, you start the interface definition by using the appropriate VB.NET keyword, Interface, and end it by prefixing the associated keyword with the word End. This is just normal VB-style syntax and shouldn't surprise any VB programmer:

Interface ISteering 
  Sub TurnLeft(  )
  Sub TurnRight(  )
End Interface

With our interface specified, we can now implement it. Since our Vehicle class is an abstract base class, we must add the MustInherit keyword when we define it, explicitly telling the VB.NET compiler that this class cannot be instantiated. In VB.NET, the Class keyword allows you to define a class, and the Implements keyword allows you implement an interface. Another thing that you should be aware of is that ApplyBrakes( ) is not implemented in this class, and we have appropriately signaled this to the VB.NET compiler by using the MustOverride keyword:

MustInherit Class Vehicle 
  Implements ISteering  

  Public Sub TurnLeft(  ) Implements ISteering.TurnLeft
    Console.WriteLine("Vehicle turns left.")
  End Sub

  Public Sub TurnRight(  ) Implements ISteering.TurnRight
    Console.WriteLine("Vehicle turn right.")
  End Sub

  Public MustOverride Sub ApplyBrakes(  )

End Class

As far as language differences go, you must explicitly describe the access (i.e., public, private, and so forth) for each method separately. This is different from C++ because all members take on the previously defined access type.

Now we are ready to translate the concrete Car class. In VB.NET, you can derive from a base class by using the Inherits keyword, as shown in the following code. Since we have said that ApplyBrakes( ) must be overridden, we provide its implementation here. Again, notice that we're throwing an exception:

  Class Car 
    Inherits Vehicle

    Public Overrides Sub ApplyBrakes(  )
      Console.WriteLine("Car trying to stop.")
      throw new Exception("Brake failure!")
    End Sub 

  End Class

End Namespace

Now that we have all the pieces in place, let's define a module with an entry point, Main( ), that the CLR will execute. In Main( ), you'll notice that we're handling exceptions exactly as we did in the Managed C++ example. You should also note that this code demonstrates the use of polymorphism because we first create a Vehicle reference that refers to a Car object at runtime. We tell the Vehicle to ApplyBrakes( ), but since the Vehicle happens to be referring to a Car, the object that is stopping is the target Car object:

Public Module Driver

  Sub Main(  )

    Try

      Dim v As Lang.Vehicle  ' namespace qualifier
      v = New Lang.Car       ' v refers to a car
      v.TurnLeft(  )           ' interface usage
      v.ApplyBrakes(  )        ' polymorphism in action

    Catch e As Exception

      Console.WriteLine(e.ToString(  ))

    End Try

  End Sub

End Module

This simple program demonstrates that we can take advantage of .NET object-oriented features using VB.NET. Having seen this example, you should see that VB.NET is very object oriented, with features that map directly to those of Managed C++ and other .NET languages.

3.2.3 C# Code

As you've just seen, VB.NET is a breeze compared to Managed C++, but VB.NET is not the only simple language in .NET—C# is also amazingly simple. Developed from the ground up, C# supports all the object-oriented features in .NET. It maps so closely to the Java and C++ languages that if you have experience with either of these languages, you can pick up C# and be productive with it immediately.

Microsoft has developed many tools using C#; in fact, most of the components in Visual Studio .NET and the .NET class libraries were developed using C#. Microsoft is using C# extensively, and we think that C# is here to stay.[5]

[5] To learn more about C#, check out O'Reilly's C# Essentials, Second Edition, by Ben Albahari, Peter Drayton, and Brad Merrill; the forthcoming C# in a Nutshell, Second Edition, by Peter Drayton, Ben Albahari, and Ted Neward; and Programming C#, Third Edition, by Jesse Liberty.

Having said that, let's translate our previous program into C# and illustrate all the features we want to see. Again, we start by defining a namespace. As you can see, the syntax for C# maps really closely to that of Managed C++:

using System;

namespace Lang 
{

Following is the IStreering interface specification in C#. Since C# was developed from scratch, we don't need to add any funny keywords like _ _gc and _ _interface, as we did in the Managed C++ version of this program:

interface ISteering 
{
  void TurnLeft(  );
  void TurnRight(  );
}

Having defined our interface, we can now implement it in the abstract Vehicle class. Unlike Managed C++ but similar to VB.NET, C# requires that you explicitly notify the C# compiler that the Vehicle class is an abstract base class by using the abstract keyword. Since ApplyBrakes( ) is an abstract method—meaning that this class doesn't supply its implementation—you must make the class abstract, otherwise the C# compiler will barf at you. Put another way, you must explicitly signal to the C# compiler the features you want, including abstract, public, private, and so forth, each time you define a class, method, property, and so on:

abstract class Vehicle : ISteering  
{
  public void TurnLeft(  )
  {
    Console.WriteLine("Vehicle turns left."); 
  }

  public void TurnRight(  )
  {
    Console.WriteLine("Vehicle turn right."); 
  }

  public abstract void ApplyBrakes(   );
}

Here's our Car class that derives from Vehicle and overrides the ApplyBrakes( ) method declared in Vehicle. Note that we are explicitly telling the C# compiler that we are indeed overriding a method previously specified in the inheritance chain. You must add the override modifier, or ApplyBrakes( ) will hide the one in the parent class. Otherwise, we are also throwing the same exception as before:

  class Car : Vehicle 
  {
    public override void ApplyBrakes(  )
    {
      Console.WriteLine("Car trying to stop.");
      throw new Exception("Brake failure!");
    }
  }

} // This brace ends the Lang namespace.

Finally, here's a class that encapsulates an entry point for the CLR to invoke. If you look at this code carefully, you'll see that it maps directly to the code in both Managed C++ and VB.NET:

class Drive
{
  public static void Main(  ) 
  {
    try
    {
      Lang.Vehicle v = null;    // Namespace qualifier
      v = new Lang.Car(  );     // v refers to a car
      v.TurnLeft(  );           // Interface usage
      v.ApplyBrakes(  );        // Polymorphism in action
    }
    catch(Exception e)
    { 
      Console.WriteLine(e.ToString(  ));
    }
  }
}

There are two other interesting things to note about C#. First, unlike C++ but similar to Java, C# doesn't use header files.[6]

[6] If you've never used C++, a header file is optional and usually contains class and type declarations. The implementation for these classes is usually stored in source files.

Second, the C# compiler generates XML documentation for you if you use XML comments in your code. To take advantage of this feature, start your XML comments with three slashes, as in the following examples:

/// <summary>Vehicle Class</summary>
/// <remarks>
///   This class is an abstract class that must be
///   overridden by derived classes.
/// </remarks>
abstract class Vehicle : ISteering  
{
  /// <summary>Add juice to the vehicle.</summary>
  /// <param name="gallons">
  ///   Number of gallons added.
  /// </param>
  /// <return>Whether the tank is full.</return>
  public bool FillUp(int gallons)
  {
    return true;
  }
}

These are simple examples using the predefined tags that the C# compiler understands. You can also use your own XML tags in XML comments, as long as your resulting XML is well formed. Given that you have a source code file with XML comments, you can automatically generate an XML-formatted reference document by using the C# compiler's /doc: option, as follows:

csc /doc:doc.xml mylangdoc.cs

Although we didn't specify the types of our parameters in the XML comments shown previously, the C# compiler will detect the correct types and add the fully qualified types into the generated XML document. For example, the following generated XML listing corresponds to the XML comments for the FillUp( ) method. Notice that the C# compiler added System.Int32 into the generated XML document:

<member name="M:Lang.Vehicle.FillUp(System.Int32)">
  <summary>Add juice to the vehicle.</summary>
  <param name="gallons">
    Number of gallons added.
  </param>
  <return>Whether the tank is full.</return>
</member>

Now that you have the generated XML document, you can write your own XSL document to translate the XML into any visual representation you prefer.

3.2.4 J# Code

Shipped with .NET Framework 1.1 (and thus with Visual Studio .NET 2003), J# is a Java language that targets the CLR. For completeness, here's the same program in J#, demonstrating that J# also supports the same object-oriented features that we've been illustrating. We simply took the preceding C# program and made a few minor changes, resulting in the J# program that we are about to examine.

Let's first look at the namespace declaration. Instead of using the keyword namespace, Java uses the keyword package, which is conceptually equivalent to the namespace concept we've been observing, since the purpose of a package is to prevent name conflicts:

package Lang;

import System.Console;

The interface specification for ISteering in J# looks exactly equivalent to the one written in C#:

interface ISteering 
{
  void TurnLeft(  );
  void TurnRight(  );
}

For the Vehicle class, there are two changes, which are shown in bold. First, the keyword implements is used to declare that a class implements one or more interfaces. Second, since Java requires thrown exceptions to be explicitly declared within the method signature, we've added this declaration in the ApplyBrakes( ) method:

abstract class Vehicle implements ISteering  
{
  public void TurnLeft(  )
  {
    Console.WriteLine("Vehicle turns left."); 
  }

  public void TurnRight(  )
  {
    Console.WriteLine("Vehicle turn right."); 
  }

  public abstract void ApplyBrakes(  ) throws Exception;
}

There are also two changes for the Car class, which are shown in bold. The extends keyword is used to declare that a class derives from (or extends) another class. The declaration for ApplyBrakes( ) must match it's parents signature, so we've explicitly indicated that an exception may be thrown from this method, as shown in bold:

// extends - used to derive from a base class.
class Car extends Vehicle 
{
  public void ApplyBrakes(  ) throws Exception
  {
    Console.WriteLine("Car trying to stop.");
    throw new Exception("Brake failure!");
  }
}

Finally, we've made one minor change in the Drive class: we simply changed Main( ) to main( ), as required by J#:

class Drive
{
  public static void main(  ) 
  {
    try
    {
      Lang.Vehicle v = null;    // Namespace qualifer
      v = new Lang.Car(  );     // v refers to a car
      v.TurnLeft(  );           // Interface usage
      v.ApplyBrakes(  );        // Polymorphism in action
    }
    catch(Exception e)
    { 
      Console.WriteLine(e.ToString(  ));
    }
  }
}

Like C#, J# supports all the object-oriented concepts we've been studying. Also, J# and C# are syntactically very similar.

3.2.5 Intermediate Language (IL) Code

Since all languages compile to IL, let's examine the IL code for the program that we've been studying. As explained in Chapter 2, IL is a set of stack-based instructions that supports an exhaustive list of popular object-oriented features, including the ones that we've already examined in this chapter. It is an intermediary step, gluing .NET applications to the CLR.

Let's start by looking at the namespace declaration. Notice the .namespace IL declaration allows us to create our Lang namespace. Similar to C#, IL uses opening and closing braces:

.namespace Lang
{

Now for the IStreering interface. In IL, any type that is to be managed by the CLR must be declared using the .class IL declaration. Since the CLR must manage the references to an interface, you must use the .class IL declaration to specify an interface in IL, as shown in the following code listing:

.class interface private abstract auto ansi ISteering
{
  .method public hidebysig newslot virtual abstract 
          instance void TurnLeft(  ) cil managed
  {
  } // End of method ISteering::TurnLeft

  .method public hidebysig newslot virtual abstract 
          instance void TurnRight(  ) cil managed
  {
  } // End of method ISteering::TurnRight

} // End of class ISteering

In addition, you must insert two special IL attributes:

interface

Signals that the current type definition is an interface specification.

abstract

Signals that there will be no method implementations in this definition and that the implementer of this interface must provide the method implementations for all methods defined in this interface.

Other attributes shown in this definition that aren't necessarily needed to specify an interface in IL include the following:

private

Because we haven't provided the visibility of our interface definition in C#, the generated IL code shown here adds the private IL attribute to this interface definition. This means that this particular interface is visible only within the current assembly and no other external assembly can see it.

auto

Tells the CLR to perform automatic layout of this type at runtime.

ansi

Tells the CLR to use ANSI string buffers to marshal data across managed and unmanaged boundaries.

Now you know how to specify an interface in IL. Before we proceed further, let's briefly look at the attributes in the .method declarations—at least the attributes that we haven't examined, including:

newslot

Tells the JIT compiler to reserve a new slot in the type's vtbl, which will be used by the CLR at runtime to resolve virtual-method invocations.

instance

Tells the CLR that this method is an instance or object-level method, as opposed to a static or class-level method.

Having specified the ISteering interface in IL, let's implement it in our Vehicle class. As you can see in the following code fragment, there's no surprise. We extend the System.Object class (indicated by the extends keyword) and implement Lang.ISteering (as indicated by the implements keyword):

.class private abstract auto ansi beforefieldinit Vehicle
       extends [mscorlib]System.Object
       implements Lang.ISteering
{
  .method public hidebysig newslot final virtual 
          instance void TurnLeft(  ) cil managed
  {
    // IL code omitted for clarity
  } // End of method Vehicle::TurnLeft

  .method public hidebysig newslot final virtual 
          instance void TurnRight(  ) cil managed
  {
    // IL code omitted for clarity
  } // End of method Vehicle::TurnRight

  .method public hidebysig newslot virtual abstract 
          instance void ApplyBrakes(  ) cil managed
  {
  } // End of method Vehicle::ApplyBrakes

  // .ctor omitted for clarity

} // End of class Vehicle

Notice also that this class is an abstract class and that the ApplyBrakes( ) method is an abstract method, similar to what we've seen in the previous examples. Another thing to note is the final IL attribute in the .method declarations for both TurnLeft( ) and TurnRight( ). This IL attribute specifies that these methods can no longer be overridden by subclasses of Vehicle. Having seen all these attributes, you should realize that everything in IL is explicitly declared so that all components of the CLR can take advantage of this information to manage your types at runtime.

Now let's look at the Car class that derives from the Vehicle class. You'll notice that in the ApplyBrakes( ) method implementation, the newobj instance IL instruction creates a new instance of the Exception class. Next, the throw IL instruction immediately raises the exception object just created:

.class private auto ansi beforefieldinit Car
       extends Lang.Vehicle
  {
    .method public hidebysig virtual instance void
            ApplyBrakes(  ) cil managed
    {
      // IL code omitted for clarity
      newobj instance void 
         [mscorlib]System.Exception::.ctor(class System.String)
      throw

    } // End of method Car::ApplyBrakes

    // .ctor omitted for clarity

  } // End of class Car

} // End of namespace Lang

Finally, let's look at our Main( ) function, which is part of the Drive class. We've removed most of the IL code—which you've already learned—from this function to make the following code easier to read, but we've kept the important elements that must be examined. First, the .locals directive identifies all the local variables for the Main( ) function. Second, you can see that IL also supports exception handling through the .try instruction. In both the .try and catch blocks, notice that there is a leave.s instruction that forces execution to jump to the IL instruction on line IL_0024, thus leaving both the .try and catch blocks:

.class private auto ansi beforefieldinit Drive
       extends [mscorlib]System.Object
{
  .method public hidebysig static void Main(  ) cil managed
  {
    .entrypoint
    // Code size       37 (0x25)
    .maxstack  1

    .locals (class Lang.Vehicle V_0,
             class [mscorlib]System.Exception V_1)
    .try
    {
      // IL code omitted for clarity
      leave.s    IL_0024
    }  // End .try
    catch [mscorlib]System.Exception 
    {
      // IL code omitted for clarity
      leave.s    IL_0024
    }  // End handler
    IL_0024:  ret
  } // End of method Drive::Main

  // .ctor omitted for clarity

} // End of class Drive

As you can see, all the major concepts that we've examined apply intrinsically to IL. Since you've seen Managed C++, VB.NET, C#, J#, and IL code that support these features, we won't attempt to further convince you that all these features work in other languages that target the CLR.

    [ Team LiB ] Previous Section Next Section