Chapter 3. .NET Programming

Now that you know what .NET is all about, let’s talk about programming for the .NET environment. This chapter presents the common programming model that .NET provides, the core languages and features that .NET supports, and language integration—how you can take advantage of object-oriented features even across different languages that target the CLR.

Common Programming Model

Without the .NET Framework, programmers must choose from a wealth of APIs or libraries that support system services. For example, if you want to write GUI applications on Windows, you have a slew of options from which to choose, including the Win32 API, MFC, ATL, VB, and so on. Once you’ve chosen the library, you have to learn how to use the structures, classes, functions, interfaces, and so forth that the library provides. Unfortunately, this knowledge doesn’t transfer directly into a different environment. For instance, there’s a big difference between the code to manage IO in MFC and the code to manage IO in VB.

One of the goals of the .NET Framework is to bring commonality to application development by providing a framework of common classes to developers who are using compilers that generate IL. This set of classes, known as the Base Class Library (BCL), is extremely helpful: if you know how to take advantage of IO functionality in .NET using your favorite language, you can easily port that code to another language. This is possible because the namespaces, classes, methods, and so forth are equally accessible in all languages. For example, you can output a line of text to the console the same way across all .NET languages by using the WriteLine( ) method of the Console object, as we have seen elsewhere in this book. This consistent framework requires less development training and enables higher programmer productivity.

Since a full discussion of the entire set of classes in the .NET BCL is beyond the scope of this book (see O’Reilly’s In a Nutshell .NET series), we talk about the System.Object class and present the major namespaces in the .NET Framework, opening the doors for you to step into this world.

System.Object

Every type in .NET is an object, meaning that it must derive directly or indirectly from the Object class. If you don’t specify a base class when you define a class, the compiler will inject this requirement into the IL code. The Object class supports a commonality that all .NET classes inherit and, thus, automatically provide to their consumers. The Object class exposes the public methods listed in Table 3-1, which you can invoke on any given .NET object at runtime.

Table 3-1. Public methods of the Object class

Methods

Description

Equals( )

Compares two objects and determines whether they are equivalent (having the same content).

ReferenceEquals( )

Compares two object references and determines whether they are referring to the same object in memory.

GetHashCode( )

Gets the object’s hash code. Hash codes are used as an added mechanism for determining object uniqueness at runtime. For instance, if you want your objects to be used as keys in a hashtable, you must override this function and provide a unique hash value for each instance of your class.

GetType( )

Obtains the object’s type at runtime. Once you have obtained the object’s type, you can obtain everything about that type using the Reflection API, as explained in Chapter 2.

ToString( )

Gets a string representation of the object. Often used for debugging purposes, this method spits out the fully qualified class name by default.

Examine the following program, which illustrates the use of all these methods:

using System;
namespace Cpm
{
  class CPModel
  {
    public static void Main(  ) 
    {
      CPModel c = new CPModel(  );

      // Test for self equivalence.
      Console.WriteLine("Equivalence:	" + 
        c.Equals(c)
      );

      // Get the hash code from this object.
      Console.WriteLine("Object hash:	" + 
        c.GetHashCode(  )
      );

      // Use the type to obtain method information.
      Console.WriteLine("Object method:	" + 
        c.GetType(  ).GetMethods(  )[1]
      );

      // Convert the object to a string.
      Console.WriteLine("String representation:	" + 
        c.ToString(  )
      );
    }
  }
}

If you compile and run this C# program, you get the following output:

Equivalence:    True
Object hash:    2
Object method:  Boolean Equals(System.Object)
Object dump:    Cpm.CPModel

The boldface line displays the second method of the CPModel class. If you look back at the program’s code, you’ll see that we use the GetType( ) method to get the type, and then we use the GetMethods( ) method to retrieve the array of methods supported by this type. From this array, we pull off the second method, which happens to be Equals( ), a method that’s implemented by System.Object.

As you can see, the System.Object class provides a mechanism for runtime type identification, equivalence, and inspection for all .NET objects.

Major Namespaces

Table 3-2 is a short list of important namespaces and classes in the .NET Framework that provide support for almost any application that you will develop. These are the namespaces that you’ll find yourself using again and again the more you develop .NET applications. For more information, consult MSDN Online or your SDK documentation, as a detailed discussion of these namespaces and classes is beyond the scope of this book.

Table 3-2. Important .NET namespaces and classes

Namespace

Description

System

Includes basic classes almost every program will use. Some simple classes that belong in this namespace are Object, Char, String, Array, and Exception. This namespace also includes more advanced classes such as GC and AppDomain.

System.IO

Provides a set of classes to support synchronous and asynchronous IO manipulation for data streams. Also provides classes that allow you to manipulate the file system, such as creating, managing, and deleting files and directories. Some of these classes are FileStream, MemoryStream, Path, and Directory.

System.Collections

Includes a set of classes that allow you to manage collections of objects. Some of these classes are ArrayList, DictionaryBase, Hashtable, Queue, and Stack.

System.Threading

Includes a set of classes that support multithreaded programming. Some of these classes are Thread, ThreadPool, Mutex, and AutoResetEvent.

System.Reflection

Includes a set of classes that support dynamic binding and type inspection. Some of these classes are Assembly, Module, and MethodInfo.

System.Security

Includes a set of classes and child namespaces that provide security support. The interesting child namespaces include Cryptography, Permissions, Policy, and Principal.

System.Net

Includes a set of classes and child namespaces that provide support for network programming. Some of these classes are IPAddress, Dns, and HttpWebRequest.

System.Data

Contains classes for ADO.NET. See Chapter 5.

System.Web.Services

Contains classes for XML web services. See Chapter 6.

System.Web.UI

Contains classes for ASP.NET web pages. See Chapter 7.

System.Windows.Forms

Contains classes for Windows user interface applications. See Chapter 8.

Keep in mind that if you know how to use any of the classes in these namespaces, you can write the code to take advantage of them in any language that targets the CLR, because the class and method names remain consistent across all .NET languages.

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.

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]

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.

Tip

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.

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.

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]

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.

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]

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]

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.

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.

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.

Language Integration

In the previous section, we saw that you can take advantage of .NET object-oriented concepts in any .NET language. In this section, we show that you can take advantage of language integration—the ability to derive a class from a base that is specified in a totally different language; to catch exceptions thrown by code written in a different language; or to take advantage of polymorphism across different languages, and so forth.

Before we discuss the examples in this section, let’s first understand what we want to accomplish (see Figure 3-1). We will first use Managed C++ to develop a Vehicle class that is an abstract base class. The Vehicle class exposes three polymorphic methods, including TurnLeft( ), TurnRight( ), and ApplyBrakes( ). We will then use VB.NET to develop a Car class that derives from Vehicle and overrides these three virtual methods. In addition, we will use C# to develop the Plane class that derives from Vehicle and overrides these three virtual methods.

Polymorphism across languages
Figure 3-1. Polymorphism across languages

In the upcoming code example, we can tell a Vehicle to TurnLeft( ) or TurnRight( ), but what turns left or right depends on the target object, whether a Car or a Plane. Unlike the examples in the previous section, the examples here illustrate that we can inherit classes and call virtual functions from ones that are defined in another language. In addition, we will demonstrate in our test program that exception handling works across different languages.

Vehicle Class in Managed C++

Let’s use Managed C++ to develop the Vehicle class, which is an abstract base class because ApplyBrakes( ) is a pure virtual function. Vehicle implements the ISteering interface to support turning left and turning right. Since the ApplyBrakes( ) function is a pure virtual function, any concrete derivative of Vehicle must implement this method:

#using <mscorlib.dll>
using namespace System;

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

public _  _gc class Vehicle : public ISteering  
{
  public:

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

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

    virtual void ApplyBrakes(  ) = 0; 
};

Given this abstract base class, we can create a DLL that hosts this definition. The first command here shows how we use the Managed C++ compiler to compile (as indicated by the /c option) the vehicle.cpp file, which contains the previous code. The second command shows how we use the C++ linker to create a DLL with metadata and IL code:

cl /CLR /c vehicle.cpp
link -dll /out:vehicle.dll vehicle.obj

Given just a few lines of Managed C++ code, we can build a DLL that can be used by another component. Note that there is no need to provide code for IUnknown, DllGetClassObject( ), DllCanUnloadNow( ), DllRegisterServer( ), DllUnregisterServer( ), and so forth. In the old days, you had to provide code for these functions and interfaces for legacy COM DLLs.

Car Class in VB.NET

Given this abstract Vehicle class, the Car class can derive from it and provide the implementation for the three virtual methods defined by Vehicle. In the following code, note that we’ve overridden and provided the implementation for TurnLeft( ), TurnRight( ), and ApplyBrakes( ). The ApplyBrakes( ) method is special in that it throws an exception, which will be caught by code written in J#, as we’ll see later:

Imports System

Public Class Car
  Inherits Vehicle

  Overrides Public Sub TurnLeft(  )
    Console.WriteLine("Car turns left.")
  End Sub

  Overrides Public Sub TurnRight(  )
    Console.WriteLine("Car turns right.")
  End Sub

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

End Class

With this code, we can build a DLL using the command-line VB.NET compiler, as follows:

vbc /r:vehicle.dll /t:library /out:car.dll car.vb

Since we want the VB.NET compiler to generate a DLL, we must signal this by using the /t:library option. Also, since Car derives from Vehicle, the VB.NET compiler must resolve the references to Vehicle. We can tell the VB.NET compiler the location of external references using the /r: option. It is important to note that you don’t need to have the source code for the vehicle DLL to reuse its code because all type information can be obtained from any .NET assembly. In addition, you should note that from this example, we have proven that you can derive a VB.NET class from a Managed C++ class.

Plane Class in C#

Now let’s use C# to develop the Plane class, which derives from the Vehicle class written in Managed C++. Similar to the Car class, the Plane class implements the three virtual functions from the Vehicle class. Unlike the Car class, though, the ApplyBrakes( ) method of this class doesn’t throw an exception:

using System;

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

  override public void TurnRight(  )
  {
    Console.WriteLine("Plane turns right.");
  }

  override public void ApplyBrakes(  )
  {
    Console.WriteLine("Air brakes being used.");
  }
}

You can build a DLL from this code using the following command:

csc /r:vehicle.dll /t:library /out:plane.dll plane.cs

Notice that we have used the /r: option to tell the C# compiler that Vehicle is defined in vehicle.dll.

Test Driver in J#

Having developed vehicle.dll, car.dll, and plane.dll, we are now ready to demonstrate that polymorphism and exception handling work across different languages. Written in J#, the next code listing contains a main( ) method with a Vehicle reference and an exception handler.

Inside the try block, we first instantiate a Plane class and refer to this instance using the local Vehicle reference. Instead of telling the Plane to TurnLeft( ) or ApplyBrakes( ), we tell the Vehicle to do so. Similarly, we instantiate a Car and refer to this instance using the local Vehicle reference. Again, instead of telling the Car to TurnLeft( ) or ApplyBrakes( ), we tell the Vehicle to do so. In both cases, we tell the Vehicle either to TurnLeft( ) or ApplyBrakes( ), but the actual vehicle that employs TurnLeft( ) or ApplyBrakes( ) is the Plane instance in the first case and the Car instance in the second case; that’s polymorphism, and it works across languages.

You should note that the second call to ApplyBrakes( ) would cause an exception because we threw an exception from Car’s ApplyBrakes( ). Although Car’s ApplyBrakes( ) was written using VB.NET, we could still catch the exception that it’s throwing in J#, proving that exception handling works across languages:

class TestDrive 
{
  public static void main(  ) 
  {
    Vehicle v = null;  // Vehicle reference

    try
    {
      Plane p = new Plane(  );
      v = p; 
      v.TurnLeft(  );
               v.ApplyBrakes(  );

      Car c = new Car(  );
      v = c; 
      v.TurnLeft(  );
               v.ApplyBrakes(  );  // Exception
    }
    catch(System.Exception e)
    { 
      System.Console.WriteLine(e.ToString(  ));
    }

  }
}

If you want to test out these features, you can create an EXE using the following command:

vjc /r:vehicle.dll;car.dll;plane.dll /t:exe /out:drive.exe drive.jsl

Since we have used the Vehicle, Car, and Plane classes in this code, we must include references to vehicle.dll, car.dll, and plane.dll. And since we are building an EXE, we need to signal this to the J# compiler using the /t:exe option. Once you have built this EXE and executed it, you get the following output:

Plane turns left.
Air brakes being used.
Car turns left.
Car trying to stop.
System.Exception: Brake failure!
   at Car.ApplyBrakes(  )
   at TestDrive.main(  )

As expected, the plane first turns left and then uses its air brakes. Then the car turns left, tries to stop, but can’t, so it throws an exception that is caught in the main( ) method.

In this simple example, we have shown that you can now take advantage of inheritance, polymorphism, and exception handling across different languages that target the CLR.

Summary

We started this chapter by telling you that .NET provides a common programming model, which reduces the learning curve and increases productivity. Once you’ve learned how to do something using the classes in the .NET Framework, this knowledge will transfer to any .NET language. We then illustrated that we could write the same type of code, supporting major .NET features, in any given language that targets the CLR. Finally, we proved to you that .NET indeed supports language integration, which was never possible using Microsoft platforms and tools, prior to .NET.



[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#.

[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.

[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.

[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.

[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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.142.248