f you haven't done so already, it's time to get a handle on the Visual Basic .NET programming language. My goal in this article is to provide you with an introductory, yet intensive, look at Visual Basic .NET and the new Microsoft®.NET platform. In order to learn what Visual Basic .NET is all about, you must first understand a few core aspects of the .NET platform. This article will build your knowledge of Visual Basic .NET from the ground up, so I'll begin by discussing the new programming model and the high-level architecture of the platform's execution engine called the common language runtime (CLR). While explaining what the CLR is and how it works, I'll show a few examples using Visual Basic .NET. As you'll see, Visual Basic® has undergone a significant overhaul to accommodate the CLR and its associated programming model. Consequently, Visual Basic .NET has many new object-oriented design features and much higher levels of type safety than previous versions of Visual Basic. It is also important to know that Visual Basic .NET omits quite a few forms of syntax that were used in previous versions of Visual Basic. This means code written in Visual Basic 6.0 will not compile until you make a number of modifications. Furthermore, writing the best possible code in Visual Basic .NET usually involves using features and syntax that are not supported in Visual Basic 6.0. As a result, migrating Visual Basic 6.0 projects to Visual Basic .NET typically requires a rewrite rather than a simple port. Migrating a Visual Basic 6.0 project to Visual Basic .NET could also involve significant rewriting due to dependencies on older libraries such as the Visual Basic for Applications (VBA) runtime or ActiveX® Data Objects (ADO). To become an effective .NET programmer, you should fully embrace the shared class libraries that are built into the CLR. Visual Basic .NET is one of several new languages that have been designed specifically for the CLR and the .NET Framework. Another language that's getting a good deal of attention is C#. Like many other programmers using Visual Basic you're probably curious about how C# compares to Visual Basic .NET. Like Visual Basic .NET, C# is a language designed exclusively to target the CLR and the .NET platform. However, unlike Visual Basic .NET, C# has been designed to be especially friendly to programmers who are already proficient in C and C++. Throughout this article, I'll point out a few key differences that might lead you to prefer one of these languages over the other. However, I truly believe that either language can be used to write software that takes full advantage of the CLR and the .NET Framework. Now, let me get started by introducing the core concepts of the .NET platform.
Code written for the .NET platform runs under the control of the CLR. It's important to note that the CLR has been architected to replace the existing runtime layers of COM, Microsoft Transaction Services (MTS), and COM+ (see Figure 1). As you can see, the CLR finally eliminates the need for a Visual Basic runtime layer. Obviously, the CLR isn't going to replace COM overnight. Many companies have a considerable investment in code written for applications based on COM, MTS, and COM+. Therefore, interoperability between COM-based software and software written for the CLR will be an important issue. Microsoft has made a considerable investment to ensure that the CLR-to-COM interoperability layer works as smoothly and efficiently as possible. However, it should be clear that in the long term, Microsoft expects the majority of development for Windows to move to the CLR and the .NET platform. Code written to run exclusively under the control of the CLR is called managed code. Older code that relies on COM and the Win32® API is known as unmanaged code. Visual Basic 6.0 is only capable of producing unmanaged code, while Visual Basic .NET is only capable of producing managed code. Herein lies a fundamental difference between these two versions of Visual Basic. The Visual Basic team has created a new version of the Visual Basic compiler (VBC.EXE) for producing managed executables (DLLs and EXEs). For example, you can build a managed DLL by feeding one or more Visual Basic source code files to the Visual Basic .NET compiler. Note that, unlike previous versions of Visual Basic, by convention Visual Basic .NET source code files have a .VB extension. While it makes writing and compiling Visual Basic source code much easier, Visual Studio .NET is not a requirement for writing software with Visual Basic .NET. You can write Visual Basic .NET source code in any editor, then build your DLLs and EXEs from the command line. Visual Basic .NET eases the management of source code because you can maintain all the code for an entire project in a single source file. Unlike earlier versions of Visual Basic, you don't have to define each class in a separate .CLS file. You do, of course, have the flexibility to maintain the code for a single project in many .VB files and compile them into a single binary for distribution. Another nice new feature is that Visual Basic .NET makes it possible to automate production builds using the NMAKE.EXE utility and a MAKEFILE. Companies that maintain lots of separate source files and are continually compiling test and production builds will see this as an improvement over Visual Basic 6.0.
Let's look at what it takes to write and compile a simple console-based application with Visual Basic .NET. As you look at the code in Figure 2, keep in mind that code written for the CLR is based on the notion of managed types. This example contains two managed type definitions: MyApp and Class1. The MyApp module contains a single method named Main, which represents the entry point for this console application. The implementation of the Main method creates an instance of Class1 and calls Method1. The return value of Method1 is used to write a message to the console window. This example demonstrates a new syntactic convenience provided by Visual Basic .NET. You can now declare and initialize a variable in a single line of code. The other managed type definition in Figure 2 is Class1. This class contains a single method named Method1. Method1 also includes a new convenience provided by Visual Basic .NET: it uses the Return statement to pass its return value back to the caller. With Visual Basic .NET it's no longer necessary to assign return values using the name of the function. Finally, take a look at the syntax in Figure 2 for accessing the Console class from the CLR class libraries. Note that the code that calls the WriteLine method on the Console class is qualified with the word System. In this case, System is being used as a namespace. The concept of namespaces is very important to the CLR and, therefore, to Visual Basic .NET. You must understand how namespaces work when you need to resolve the names of managed types from other libraries. A namespace is a user-defined scope in which managed types are defined. Most of the CLR built-in types are defined within the System namespace, such as System.Object, System.Int32, and System.String. Note that a namespace can be nested within another namespace, as in the case of System.Data, which holds classes such as System.Data.DataSet. Visual Basic .NET provides a syntactic shortcut via the Imports statement when programming against types declared within a namespace. For example, suppose you add this line to the top of your Visual Basic .NET source file:
This Imports statement makes it possible to call the WriteLine method without full qualification, as shown here:
Console.WriteLine ' this can be usedSystem.Console.WriteLine ' instead of this
Console.WriteLine ' this can be used
System.Console.WriteLine ' instead of this
Note that the using statement in C# provides identical support to the Visual Basic .NET Imports statement. You should also understand that the Imports statement does nothing more than make your statements more concise when typing in the names of other managed types.
You can compile the source code from Figure 2 into a console application EXE by running the following command from the command line:
vbc.exe /target:exe hello.vb
While the code in this example is very simple, it allows me to illustrate some key aspects of developing software for the .NET platform. When you've successfully built a project with the Visual Basic .NET compiler, you have created a binary that holds one or more managed type definitions. These managed types are then ready to be loaded and run under the control of the CLR. The programming model of the CLR recognizes four primary kinds of managed types: classes, interfaces, structures, and enumerations. Figure 3 shows what each one looks like in Visual Basic .NET. Unlike previous versions of Visual Basic, Visual Basic .NET does not support user-defined types (UDTs) or the Type keyword. UDTs have been replaced with the structure type. A structure type is similar to a UDT in that it is a value type; it can be allocated on the stack or wholly embedded inside another type. Structures are a valuable alternative to classes because they can provide a more efficient way to store and pass data. It's also important to note that structure types are more versatile than UDTs were because structures can expose public methods and even implement an interface. You should think of a structure as a managed type used to create lightweight objects. Both the CLR and Visual Basic .NET have excellent support for interface-based programming. Unlike Visual Basic 6.0, you no longer have to fudge an interface definition using a class construct. Figure 3 shows the basic syntax for defining an interface and implementing it in a class. From this simple example, you should be able to see that the syntax for interface-based programming is far more elegant than the syntax in Visual Basic 6.0. Shared members is another critical concept of the CLR programming model that will be new to many programmers experienced with Visual Basic. For example, a class can contain shared methods and shared fields, in addition to instance methods and instance fields. This is very different from Visual Basic 6.0, where classes could only contain instance members. A shared member differs from an instance member in that it can be accessed without creating an instance from the class. Let's look at a simple example from Figure 3. Examine Method3 in Class1, which has been marked as a shared method. Note that the keyword Shared in Visual Basic .NET has the same meaning as the static keyword in languages such as C#, C++, and Java. A client can access a shared method simply by calling the shared method name together with the class name, like this:
Another interesting thing to note is that the programming model of the CLR has no direct mapping to the Visual Basic .NET Module type. The Visual Basic .NET programming language includes the Module type largely to provide an equivalent to .BAS modules in older versions of Visual Basic. However, when you build a DLL or an .EXE, the Visual Basic .NET compiler silently transforms each module type in your source code into a managed class that can be loaded and run by the CLR. You should think of a module as a special class type that cannot be used to create objects. It can contain only shared members; it cannot contain instance members. You have to keep on your toes, because although every member of a module is implicitly shared, you'll experience a compile-time error if you add the Shared keyword to any one. Last, you should note that a module type offers one syntactic convenience over the class type in Visual Basic .NET: you can call a shared method defined in a module without using the module name. When you call a shared method from a class, you must do so using the class name, or alternatively add an Imports statement with the class name. The programming model of the CLR also includes a few other familiar abstractions. Classes and structures use fields for defining typed units of storage and use methods to provide behavior. The CLR also recognizes properties. As you know from earlier versions of Visual Basic, a property is a method (or a set of methods) that appears to the client as an exposed field. While the syntax for declaring properties changes between Visual Basic 6.0 and Visual Basic .NET, the motivations for using them are exactly the same. The key point here is that the abstraction of properties is recognized by the underlying programming model of the CLR. You should note that the CLR, like COM and Visual Basic 6.0, supports indexed properties. As a result, you will, from time to time, see client code that looks like this:
Dim s As Strings = Object1.Property1(10)
Dim s As String
s = Object1.Property1(10)
An indexed property can also be assigned as a default property for a class. (C# uses the term "indexer" to refer to an indexed property that's been marked as default.) Here's an example of what client code looks like when accessing a default indexed property:
Dim s As Strings = Object1(10)
s = Object1(10)
Note that a property cannot be marked as the default for a class unless it is indexed. This is a big change from earlier versions of Visual Basic. Here's an example of Visual Basic 6.0 code that retrieves a non-indexed default property from a textbox:
Dim var1 As Variantvar1 = frmMain.txtCustomer
Dim var1 As Variant
var1 = frmMain.txtCustomer
Earlier versions of Visual Basic suffer from an awkward ambiguity caused by the inclusion of non-indexed default properties. How does that Visual Basic compiler know whether you intend to assign the default property of the textbox or a reference to the actual textbox object? The classic way to solve this ambiguity in Visual Basic has been to use the Set keyword to distinguish the assignment of a object reference from the assignment of the object's default property value. For example, if you want to assign a reference to the textbox object instead of its default property value, you write the following code:
Dim var1 As VariantSet var1 = frmMain.txtCustomer
Set var1 = frmMain.txtCustomer
As you have just seen, earlier versions of Visual Basic require the Set keyword due to the ambiguities caused by non-indexed default properties. Since Visual Basic .NET eliminates non-indexed default properties, the Set keyword is no longer needed. In fact, Visual Basic .NET does not support the Set keyword for assignment operations. This means you'll experience a compilation error if you use the Set keyword when assigning an object reference. You have to admit, that's a pretty big syntactic change when migrating from Visual Basic 6.0 to Visual Basic .NET.
The delegate is a new concept that's central to the programming model of the CLR. A delegate is a special type of managed class that allows you to work with type-safe function pointers. Each delegate type is based on a single method signature. When you create an instance of a delegate, you must provide the address of a method implementation with a matching signature. Once you've created the delegate instance, it's pretty simple to invoke the method. Figure 4 demonstrates the most fundamental Visual Basic .NET syntax required to declare and use a delegate. Note the use of the keywords Delegate and AddressOf. You should also see from this example that there is a longhand syntax and a more concise shorthand syntax, both of which produce the same results. Once you understand what delegates are and how they work, you can appreciate how the CLR uses them to provide sophisticated support for more advanced features, such as multicasting and events. A multicast delegate is like a collection of function pointers that facilitates the execution of a set of method implementations using a single line of code. Whenever you create a delegate using the Delegate keyword, remember that the Visual Basic .NET compiler creates a multicast delegate as opposed to a simple delegate. This gives you the ability to hook up multiple method implementations to a single delegate. The following code shows a variation on the delegate example shown in Figure 4.
Dim d1, d2, d3 As Delegate1d1 = AddressOf Sub1d2 = AddressOf Sub2' create d3 which is a multicast of d1 and d2d3 = CType(System.Delegate.Combine(d1, d2), Delegate1)d3("Firing two method implementations at once")
Dim d1, d2, d3 As Delegate1
d1 = AddressOf Sub1
d2 = AddressOf Sub2
' create d3 which is a multicast of d1 and d2
d3 = CType(System.Delegate.Combine(d1, d2), Delegate1)
d3("Firing two method implementations at once")
A third delegate, d3, is created as a combination of the other two. The last line of code in this example executes the method implementations for both Sub1 and Sub2. While this example uses a multicast delegate to fire two implementations, you can hook up and execute as many method implementations as you'd like. The CLR worries about the plumbing details of actually invoking the calls. You just have to make sure that the delegate and all the methods share a common signature. Now that you understand the basic idea of a multicast delegate, you can begin to appreciate how the CLR supports events on a managed class. The CLR event architecture is based on the idea of a source object using multicast delegates to execute method implementations on one or more listener objects. As is the case in Visual Basic 6.0, a Visual Basic .NET class can contain events in addition to methods, fields, and properties. Figure 5 shows the basic code required to register two listener classes with an event source class. In Visual Basic .NET, the way events work is similar to how they work in Visual Basic 6.0, as far as syntax is concerned. For example, Visual Basic .NET provides familiar keywords such as Event, RaiseEvent, and WithEvents. Visual Basic .NET also introduces the Handles keyword for creating listener methods. While the syntax for programming events remains largely the same, the plumbing used down below has completely changed from that of Visual Basic 6.0. Events in earlier versions of Visual Basic are based on COM and the ConnectionPoint interfaces. As I've mentioned, events in the CLR are based upon multicast delegates. A class that contains events can be used to create an event source object, which sends notifications to listener objects. The use of keywords such as Event, RaiseEvent, WithEvents, and Handles instructs the Visual Basic .NET compiler to emit lots of extra code to deal with delegate registration behind the scenes. That means you don't have to work with delegates directly when raising or listening for events. Note that much of this extra productivity is specific to Visual Basic .NET and is not included in other managed languages, such as C#. I've just taken a brief look at the different kinds of managed types that make up the programming model of the CLR. Now that I've covered some of the basics, let's take a more in-depth look at what gets compiled into a managed executable.
The CLR, as its name implies, was designed to allow for an unprecedented level of integration between all languages that target the .NET platform. This means that the Visual Basic .NET compiler, along with the compilers of other managed languages, such as C#, must follow the same set of rules. One of the most important rules is that executable instructions must be compiled into DLLs and EXEs in the form of Microsoft Intermediate Language (MSIL). MSIL is a compiled format that is both similar to and very different from traditional assembly code. It is similar to assembly code in that it contains low-level instructions where things are being pushed, popped, and moved in and out of registers. However, it is very different in that it contains no dependencies on any particular operating system and hardware platform. This means that after an EXE or DLL containing MSIL is deployed on a target computer, it must still undergo a final round of just-in-time (JIT) compilation to transform it into machine-specific assembly instructions. The first key benefit to MSIL is that it allows the CLR to verify during JIT compilation that the managed code is completely type safe. The CLR relies on this verification process to ensure that code distributed inside a DLL or EXE doesn't play tricks with pointers or illegal type conversions. This allows the CLR to protect itself from many commonly used system attacks. A computer that downloads managed code from an untrusted source can protect itself in a way that unmanaged code can't. A second obvious benefit of MSIL is that it decouples your EXEs and DLLs from any specific operating system or hardware platform. Microsoft currently has plans to ship a version of the CLR for Windows 2000, Windows NT®, Windows 98, and Windows 95. However, MSIL is powerful because it gives your DLLs and EXEs the potential of running on platforms other than those based on the Intel x86 processors. You are likely to see a version of the CLR for Windows CE in the near future. It is also entirely possible that you will see implementations of the CLR built for other operating systems and hardware platforms as well. The idea of running your Visual Basic code on a hardware platform such as a handheld device or Pocket PC is a reality today.
While you might be somewhat apprehensive about Microsoft's long-term decision to replace COM with the CLR, you should strive to understand the underlying advantages of migrating from the old runtime environment to the new one. The architects that designed the CLR and the .NET platform were able to incorporate the best aspects of COM while alleviating much of the pain of writing and deploying COM-based applications. In particular, the CLR has eliminated many of COM's most frustrating problems with regard to language interoperability, application deployment, and component versioning. As you might have guessed, the new programming model introduced by the CLR serves to eliminate many of COM's unnecessarily confusing details with regard to writing and understanding the code for a distributed application. The history of COM has been plagued with problems concerning interoperability of various languages. While a certain degree of interoperability exists between unmanaged languages, it is far from ideal. For example, it's common for C++ programmers to produce component DLLs that are unusable from Visual Basic or scripting languages. Many built-in C++ types for dealing with things such as strings, arrays, and pointers are either impossible or impractical to consume from other languages. The CLR ensures higher levels of interoperability. The programming model of the CLR is based on the universal type system shown in Figure 6. Every managed language must be layered on top of and mapped to this core set of built-in types.Figure 6 Universal Type System As you can see in Figure 6, the CLR type system defines a predictable set of primitive types containing things like integers and floating point numbers. The CLR's type system also defines standard classes for other types, such as String, Array, and Exception. Languages such as Visual Basic .NET and C# provide keywords that map directly to many of the built-in CLR types. For example, Visual Basic .NET provides the Integer keyword, which is the equivalent of the int keyword in C#. Both types map directly to the CLR's System.Int32 type. As you can see, the CLR improves upon COM by standardizing on a universal set of types that are shared across all managed languages. You should know that the CLR provides a few types and features that are not supported by every managed language. For example, the CLR's type system provides various built-in types for unsigned integers. Unsigned integers are fully supported by C#, but not by Visual Basic .NET. This means there's a potential for a C# programmer to create a component that exposes unsigned integers in a manner that would make it either awkward or impossible to access from other languages. In order to prevent situations in which programmers mistakenly create components that are inaccessible from other managed languages, Microsoft has created a document called the Common Language Specification (CLS). The CLS defines a subset of CLR types and features that component and consumer languages must support to effectively interoperate with other managed languages. Visual Basic .NET is fully compliant with the CLS. In addition, the class libraries built into the CLR are fully accessible from any CLS-compliant language, including Visual Basic .NET. This is a great news for programmers using Visual Basic who, in the past, have had to accept that many parts of their underlying platform (such as the Win32 API and OLE32.DLL) are inaccessible from their chosen language. Full access to the CLR class libraries really levels the playing field with respect to what can be done with Visual Basic when compared to other managed languages. As you can see from Figure 6, the type system of the CLR relies heavily on inheritance. The entire type system is based on a single-inheritance hierarchy. All managed types used to create objects ultimately derive from the single root type System.Object. When you create a class without explicitly inheriting from another class, your class implicitly inherits from System.Object. That means that a class declaration like this:
Public Class Class1 ' class member declarations go hereEnd Class
Public Class Class1
' class member declarations go here
is equivalent to this class declaration:
Public Class Class1 Inherits System.Object ' class member declarations go hereEnd Class
If you want to derive one user-defined class from another, the syntax looks like this:
Public Class Class2 Inherits Class1 ' class member declarations go hereEnd Class
Public Class Class2
Note that Visual Basic .NET requires you to separate the name of the deriving class and the Inherits keyword using a line break. If you'd like to write Visual Basic .NET code to purposely confuse all those know-it-all C++ programmers out there, you can substitute a colon for the line break like this:
Public Class Class2 : Inherits Class1 ' class member declarations go hereEnd Class
Public Class Class2 : Inherits Class1
This syntax more closely resembles C# and C++, where the colon is required when using inheritance. However, with Visual Basic .NET, it's important to realize that the colon is just acting as a line break. I have actually gotten hooked on this style because I find it more manageable and more readable. OK, and yes, I use it because I've always been a C++ wannabe. A key point to observe is that any managed type from which you can create an instance ultimately inherits from System.Object. This also includes primitive types such as integers, longs, and doubles. This means that all variables can be cast to the System.Object type regardless of whether they are reference types or value types. You should also keep in mind that the Visual Basic .NET language has moved the functionality of older unmanaged types such as the variant, IUnknown and IDispatch into System.Object.
The .NET Framework uses the term "module" to refer to a managed binary such as a DLL or an EXE. Every managed language must have a compiler that is capable of building an extensive set of component metadata into each module to describe the types it contains. As you can see from Figure 7, a module holds component metadata and the MSIL code for the managed types it contains.Figure 7 A .NET Module The component metadata in a module is similar to the type information stored in the type library of a COM DLL because it exposes information to client applications about its public types (such as enumerations, structures, interfaces, and classes). However, there are a few important differences that make the type information for managed components much richer than the type information used by COM. First, all component metadata must adhere to a single, high-fidelity format for describing managed type information. This eliminates problems experienced by COM developers with fidelity loss between the type information format used in type libraries and the format used in Interface Definition Language (IDL). What's more, .NET development is easier than COM development because you never need a separate language like IDL to define your types. Custom types can always be fully described using a managed language such as Visual Basic .NET or C#. Another big difference between COM and the CLR is that managed components contain far more metadata for describing classes. In COM, a class's type information is defined in a type library in terms of a coclass. The COM coclass type is limited in the sense that it only describes a class in terms of which interfaces it supports. COM has very strict rules about separating interface from implementation, and the limited information in a coclasses definition is very much in line with that philosophy. While COM requires a formal separation of interface from implementation, Visual Basic has always made things easier by automatically building a default interface behind every multiuse class. When a Visual Basic client contains a reference variable based on a class name, the Visual Basic compiler silently casts the reference to the default interface for the class. Visual Basic, therefore, has been able to hide the fact that COM requires a formal separation of interface from implementation types. The architects of the CLR have taken a view of classes that is much more in line with Visual Basic than with COM. The component metadata for a managed class can expose its public methods as part of a default interface. This offers much more flexibility. Unlike COM, you don't need to define a standalone interface in order to program against a class. The key point is that you don't have to work in terms of interfaces in situations when a class with public methods is an acceptable and much easier alternative. While the CLR architects have removed the requirement to work in terms of standalone interfaces, you should by no means interpret this to mean that interface-based programming isn't important when writing managed code. Programming in terms of explicit interfaces is as important as ever when you want to create plug-compatible classes or decouple one subsystem from another in a large scale application. Furthermore, the CLR class libraries frequently expose their functionality through interfaces. Any intermediate or advanced programmer should be very comfortable defining, implementing, and using interface types. While both COM and the CLR require components to expose public type information, the CLR is different from COM in that it requires modules to expose internal type information to the system. This internal type information is used by the CLR at runtime to create and manage objects. This allows the CLR to perform many tasks which the COM runtime delegates to component DLLs and client applications. Let's look at an example. A COM type library doesn't contain any type information to describe how objects should be represented in memory. Instead, it's the responsibility of a COM DLL to allocate and release the memory for its objects. A COM DLL also has the responsibility of laying out its objects with COM-compliant vtables. In the CLR, these responsibilities have been removed from component DLLs and transferred to the underlying runtime environment. The CLR takes on the responsibility of allocating and releasing the memory for objects. When a client makes a request to create an object from a managed class, the CLR discovers the object's memory and layout requirements by examining internal type information about the class at runtime. This allows the CLR to allocate the proper amount of memory during object creation. The CLR also uses internal type information to create the binding that allows clients to execute methods on objects. This means that managed binaries, unlike COM binaries, don't have to contain code to generate or access COM-style vtables. As you can see, the CLR takes on more responsibilities than the COM runtime. This has allowed the CLR architects to remove much of the complexity and extra baggage that is built into COM binaries, such as class factories and code for dealing with vtables.
Now that I'm on the subject of memory management, I'd like to point out an important architectural difference between COM and the CLR. It has to do with the management of object lifetimes and has a dramatic effect on the way you should write your code. COM uses reference counting to manage object lifetimes. When you release the last reference to a COM object, it synchronously removes itself from memory. If your class contains some custom cleanup code in an implementation of Class_Terminate, you get the guarantee that this code will run in a deterministic fashion. This is not the case when running managed objects in the CLR. The CLR manages object lifetime through garbage collection. This is very different from the reference counting that COM uses to manage object lifetimes. The CLR always creates objects on a garbage-collected heap. When a client releases the last reference to an object, the object is not instantly removed from memory. Instead, the garbage collector removes the object from memory at some indeterminate time in the future. The two primary reasons you would prefer garbage collection over reference counting are enhanced performance and the ability of the system to detect and break down circular references between objects. The designers of the CLR decided that these reasons were sufficient grounds for using garbage collection for lifetime management rather than using the model used by COM. The primary reason some prefer reference counting over garbage collection is that your destructor (Class_Terminate) will fire in a timely and predictable manner. Since the CLR doesn't use reference counting, Visual Basic .NET does not support Class_Terminate. Instead, a managed class can provide a Finalize method that will fire when the object is removed from memory. However, it should be clear that things are much different from COM where the destructor for a class fires the instant the client releases it. The debate about which is superior—garbage collection or reference counting—rages on. This debate has turned into a bit of a crusade for many developers. While I'll refrain from commenting on which style of lifetime management is better, I can safely say that the CLR uses garbage collection, and that fact should affect the way you write your managed code.
As I mentioned earlier, a module is a binary unit of code which holds both component metadata and MSIL. However, there is another layer of abstraction for distributing managed code called an assembly. It complements the module because it addresses several important issues related to deployment, versioning, and security. There are many important details concerning how assemblies are used to deploy managed code. Unfortunately, there are far too many details for me to cover in this article. I'll only scratch the surface, providing a high-level overview of the significant points. An assembly can be defined as one or more modules that make up a unit of deployment. Each assembly contains a catalog of component metadata known as a manifest. The abstraction of the assembly is important because its manifest holds critical metadata about type visibility, component versioning, and security. Every managed type must exist within the scope of an assembly. In Visual Basic .NET, each project you create will typically represent a single assembly. When you want to use managed types in your project from another assembly, your project must include a reference to this other assembly. When you are creating an assembly for others to use, you should decide which types should be visible from outside the assembly. The keywords Public and Private allow you to expose or hide a type such as a class or interface. Note that Visual Basic .NET allows you to adorn methods in a public class using the Friend keyword, making the method accessible only from within the assembly. One way to think of an assembly is that it is a unit of versioning. Your decision to make each type public or private is very important. Remember, you only need to consider versioning issues for types and type members that you have exposed to external clients. Types that are private to an assembly can be removed or modified without concern for existing client applications. When you're calling VBC.EXE from the command line, you must pass a switch (/reference or /r) for each external assembly your project is using. For example, here's what a call to the Visual Basic .NET complier looks like when a console application is using types from a external assembly:
vbc.exe /target:exe /reference:MyLibrary.dll hello.vb
One assembly you never need to reference explicitly is MSCORLIB.DLL. Since this assembly contains the core managed types, such as System.Object, used by every project, the Visual Basic .NET compiler automatically includes a reference to it whether you add one or not. Other assemblies must be explicitly referenced, or your code will not compile. Note that Visual Studio .NET passes the appropriate arguments to the Visual Basic .NET compiler when you create references in your project. In many cases, an assembly will consist of a single DLL or a single EXE file. By default, every DLL or EXE you build with the Visual Basic .NET compiler is both a module and an assembly. However, in a more complicated deployment scenario you might want to create an assembly that contains multiple DLLs and various resource files. There is a command-line switch for the Visual Basic .NET compiler that allows you to build a module that is not an assembly. This makes it possible to build multi-module assemblies using the assembly linker utility (AL.EXE). The CLR recognizes two types of assemblies. The first, a private assembly, is deployed with and used by a single application. Note that a private assembly must be deployed in the same directory or in a subdirectory of the application that uses it. The second, a shared assembly, can be used by multiple applications. A shared assembly must be installed in a special assembly cache before it can be used by client applications.Figure 8 Private Assembly with One DLL Figure 8 and Figure 9 provide high-level views of assemblies. Figure 8 shows a private assembly that consists of a single DLL. Figure 9 shows a more complex example of a shared assembly based on three different DLLs.Figure 9 Shared Assembly
The CLR offers many advantages over COM when it comes to application deployment and component versioning. For example, COM has gained a reputation for being fragile and hard to deploy because it requires registry entries for things like ProgIDs, CLSIDs, IIDs, and type libraries. The CLR does not require similar registry entries for assemblies or managed types. The CLR provides much more flexibility and adaptability when it comes to finding loadable modules and resolving types at runtime. The CLR also offers significant improvements over COM with respect to component versioning. This is largely due to the CLR's support for side-by-side deployment—in other words, the CLR's ability to load and work with multiple versions of the same assembly. The CLR makes it possible for two different applications to load and use two different versions of the same DLL even when they're running together inside the same process. Side-by-side deployment is a great improvement over COM where a class (a CLSID) can only be deployed once per machine. This means it's now far more acceptable to create new versions of DLLs that do not maintain backwards compatibility with earlier versions. You can simply deploy multiple versions of a DLL in order to satisfy both new clients and old clients alike. Gone are the days when installing a new version of a DLL steps on an older version, breaking an existing client application. The CLR provides sophisticated versioning support. However, it's important to know that this support is only available when you deploy your code in a shared assembly. I'll give you a brief description of how things work so you can appreciate why things are so much better than they are with COM versioning. When you compile a client application that references a shared assembly, the assembly's name and version number are recorded in the client assembly's manifest. Unlike COM, a client application always knows which version of a DLL it was compiled against. Furthermore, the CLR makes it possible for a developer or an administrator to adjust the versioning policy for a client application to determine which version of a shared assembly gets loaded. A client application can be configured to load the exact version that it was compiled against or it can be configured to load the most recent, compatible version. From this brief discussion of assemblies, you should be able to tell that the CLR provides a much improved environment for deploying applications and versioning components. You've probably heard many people at Microsoft touting this as the end of DLL Hell. From my perspective, this is one of the most tangible benefits of migrating applications to the CLR and Visual Basic .NET.
As you can see, there are countless design issues and implementation details to consider when deciding whether to migrate from Visual Basic 6.0 and COM over to Visual Basic .NET and the CLR. Migrating will have its fair share of costs and benefits. You should also consider the differences between migrating a development team as opposed to migrating an existing Visual Basic 6.0 project. Keep in mind that the programming model of the CLR supports many new object-oriented features in Visual Basic that will be new to developers. There are far more new programming features and concepts than I could possibly cover in this article. For starters, Visual Basic .NET includes support for structured error handling, shared class members, parameterized constructors, method overloading, and implementation inheritance. Make no mistake about what it will take to migrate the average programmer from Visual Basic 6.0 to Visual Basic .NET. All these new object-oriented features are going to take time to master. There's a great deal to learn in order to use all these new features properly . Keep in mind that migrating to Visual Basic .NET is not just about changing the way you write your syntax. You are also encouraged to use the built-in class libraries of the CLR whenever possible. These class libraries provide a wide range of functionality in areas such as string manipulation, user interface construction, database access, XML processing, and sockets programming. Embracing the class libraries of the CLR requires a fundamental shift for programmers whose experience is with Visual Basic. When you need to manipulate text, you will be tempted to use familiar functions such as UCase, InStr, and StrComp from the VBA runtime library. However, you should resist this temptation and seek out the equivalent functionality from the CLR class libraries. As you can imagine, migrating programmers from existing libraries such as the VBA runtime, ADO, and MSXML to similar functionality in the CLR class libraries will have its associated costs. Once you're up to speed with the new features of Visual Basic .NET and start to get comfortable with the CLR class libraries, I think you'll agree that they provide a much better platform for building distributed applications than anything you've ever used before. When you reach this point, I can say with confidence that you'll be very excited about using Visual Basic .NET whenever you start a new project. However, it's not so easy to decide whether your current Visual Basic 6.0 project should be migrated over to Visual Basic .NET. Porting any project from Visual Basic 6.0 to Visual Basic .NET will be a nontrivial undertaking. Migrating an application or component library will require redesigning existing types and rewriting existing method implementations. Eliminating references to unmanaged libraries and replacing them with references to the CLR class libraries obviously makes migration all the more costly. Some companies will come to the conclusion that it's simply not worth trying to port their existing Visual Basic 6.0 projects over to Visual Basic .NET. At this point, you have two options. You can rewrite the project from scratch in Visual Basic .NET or you can simply leave the project as it is in Visual Basic 6.0. If you decide to leave some of your applications and component libraries in Visual Basic 6.0, you'll be happy to discover that the CLR-to-COM interoperability layer is reliable and fairly easy to use. The CLR-to-COM interoperability layer gives you the opportunity to build applications using a mix of managed and unmanaged code. This means you can mix and match Visual Basic .NET code with Visual Basic 6.0 code. I plan to cover many of these issues in far more depth in an upcoming Basic Instincts column.
There is no way you can understand what Visual Basic .NET is all about until you have a firm grasp of the CLR and its associated programming model. It's important that you learn about the underlying type system and object-oriented features of the CLR. Once you have learned these basics, you will be able to master a managed language such as Visual Basic .NET or C#. Throughout this article I have described how many of the implementation details of COM have been replaced with newer, more modern implementations in the CLR. However, the spirit of COM is still very much alive in the CLR and the .NET Framework. It's all about writing, reusing, deploying, and versioning application code based on components. If it helps, you can simply think of the CLR as the most recent version of COM.
From the February 2001 issue of MSDN Magazine
More MSDN Magazine Blog entries >
Browse All MSDN Magazines
Subscribe to MSDN Flash newsletter
Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.