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 three core Microsoft .NET languages, including Managed C++, VB.NET, and C#, and several core programming concepts that all .NET languages support, including:
Mitigates name collisions.
Specifies the methods and properties that must be implemented by objects that expose the interface.
In object-oriented languages, allows a class to encapsulate all its data and behavior.
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.
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.
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.
While 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, and C#. These concepts are nothing new: we’re merely demonstrating how they’re represented in all .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++ is essentially Microsoft’s 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[15] that is inherent in C++ programs, and at the same time, you can also take advantage of CLR features.[16]
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 two lines 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
. This new keyword 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 (meaning that
it’s polymorphic) 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. 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.
_ _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.
Now that we have a concrete class, we can write the main( ) function
to test our Car class. You’ll 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
[17]
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 the major .NET 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#, and IL, just to prove that these concepts can be represented the same way in all .NET languages.
Microsoft has revamped VB and added full features for object-oriented programming. The new VB language, VB.NET, allows you to do all that you can 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 is there to simplify your life as a programmer.
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, 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, as shown here in bold:
Imports SystemNamespace Lang
Next, we specify the ISteering interface, which is extremely 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 SubPublic 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 VehiclePublic 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 qualifer v = New Lang.Car ' v refers to a car v.TurnLeft( ) ' inteface 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 all the .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.
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 were developed using C#. Microsoft is using C# extensively, and we think that C# will be the language of the next decade.[18]
Having said that, let’s translate our previous program into C# and illustrate all the features we want to see. Again, first 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, such as _ _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 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( )); } } }
There are two other interesting things to note about C#. First, unlike C++ but similar to Java, C# doesn’t use header files.[19] 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 /// overriden 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 C# tags. 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.
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.
You’ll notice that 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:
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.
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:
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 Vehicleextends [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 that you see 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 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 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 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
block, 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 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 handlerIL_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 intrinsically apply to IL. Since you’ve seen Managed C++, VB.NET, C#, and IL code that support these features, we won’t attempt to further convince you that all these features work in other .NET languages.
[15] You can easily mix managed and unmanaged code in C++ programs. The unmanaged code will perform better.
[16] 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#.
[17] 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.
[18] If you want to learn more about C#, check out O’Reilly’s C# Essentials (Albahari, Drayton, and Merrill, 2001), C# in a Nutshell (Drayton and Albahari, 2001), and Programming C# (Liberty, 2001).
[19] 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.
18.225.56.233