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:
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 combine 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 patterntotally
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.
|
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 .NETC# 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 methodmeaning that this class
doesn't supply its implementationyou 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:
Signals that the current type definition is an interface
specification.
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:
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.
Tells the CLR to perform automatic layout of this type at runtime.
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 declarationsat least the attributes
that we haven't examined, including:
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.
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 codewhich you've
already learnedfrom 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.