[Changes in VB.NET]


Thursday, March 01, 2001


Microsoft has designed VB.NET so substiantially different from classic VB that many people speak of it as a new language rather than another version. This is a fair assessment, but then, Basic has a long history of dramatic revisions: think of GW-Basic, QuickBasic, and Visual Basic. It has never had the level of language stability that C has had. That's both good and bad: it's good to break with outdated concepts (just think of all the cowboyish C code that is allowed to exist in .cpp files because C++ is largely compatible with it); it's bad because of the obvious migration issues. While talking history, I could also add that Basic has always been "late" in catching up - think of concepts such as procedural programming or recently, object-oriented programming. This has resulted in changes many times (though these changes have been additions mainly).


In any case, forget about porting an existing app to VB.NET. I'm not saying it's impossible, but it's better to rearchitecture the application (which might often mean to write the app or parts of it from scratch), or use .NET for new projects only. While you can avoid many of the new version's incompatibilies when following certain coding principles, this will not solve all the problems. For example, parameters are now passed ByVal by default - avoiding the default and being explicit (ByVal/ByRef) will take care of this issue; but what do you do about the new forms engine? How do you take advantage of new controls, the .NET framework with its classes, and a better overall design? In classic VB, apps were full of hacks and workarrounds, many of which have been fixed in VB.NET. You don't want to port those hacks and workarrounds to .NET, much less have the Upgrade Wizard recode them. Even Microsoft has admitted it won't do a complete conversion job, but rather leave many "TODO:" comments in the source code. And while the wizard seems to cope fine with changes that cause the most gripe in the VB community, such as changes in data types, it leaves much to be desired. For example, the wizard produces code to replace the hidden global form variables (which were actually a compatibility hack even in classic VB), and fails miserably at context menus, user controls, and API calls (to name just a few problems).

Rewriting existing apps should be faster than converting them; often it's also done in less time than it took to first create them. Many design concepts have a finite lifetime - after a few revisions, an app's old foundation cannot handle new features you wish to add to it. If that is the case, a rewrite is a good opportunity to create a better solution. If it's not the case, however, it might be better to leave it in classic VB: although VB6 will not be improved as a development tool, you can still improve the apps written with it. What's more, both .NET and COM apps can exist side-by-side, and even interoperate.

Porting might be easiest for code components that carry out some abstract task (must not be a workarround for classic VB's limitation; should not be an API wrapper; should not be too big). My CHistory class is a good candidate for porting to VB.NET (on the other hand, I can use it "as is" because it lives in a COM component, but then again, I've found that it benefitted from a rewrite anyway).

Anyway: what's going on

VB.NET is not something like "VB7". The .NET framework, which is touted to eventually replace COM a middleware layer between the operating system and applications (to a great extent for third-party software development anyway, though not necessarily for System APIs), as well as rid us of language-specific supported DLLs (such as the VB runtime), is the "next big thing". Languages can freely interoperate: a VB.NET class can inherit from a class written in (so-called "managed") C++, and vice versa. Writing a low-level component in C for consumation in VB.NET is now as easy as eating a piece of cherry pie. And forget about reference counting, GUIDs, and DLL hell. These underlying changes, however, mean that old code is severely broken.

The .NET framework is largely seen an MS's answer to Java (rightly or wrongly). The existance of the J language also explains why MS developed a new language, called C# (pronounced "C sharp"), which feels a lot like Java. Still, C# isn't nearly as crippled: it supports delegates, structs, unions, pointers, custom operators and conversions, ByRef parameter passing, and much more. C# has also inherited a few civilized-language features from Basic, such as For-Each, properties, and ParamArrays, although it keeps many doubtful K&R features. It's by far the C-style language I like best, and I'm sure I'll be using it in addition to VB.NET.

Otherwise, the language lawyers are already running at full throttle, fueled by a perceived rivalry between VB.NET and C#. This is a sure sign that both are very similiar (and evil-spirited people say that VB.NET had better be called B#).

Language changes

Let's look at those changes that affect the core of the language, first. They're the bulk of it, but they're not necessarily the biggest problem. Many people will welcome at least a part of these design decisions, but let's note that not all of them have been necessary (necessities arising from the fact that VB.NET is a client of the Common Language Runtime).


Initialization syntax

You can now initialize variables in the declaration statement. It works the same as in constants or default values for optional parameters:

' Initialize both i as s
Dim i As Integer = 5, s As String = "Hello"

If you create an object, you can use this initialization syntax in order to feed arguments to a class's constructor (see below).

Option Strict

There is a new Option, called Strict. Basicly, it means you cannot implicitly convert or cast from one type to another (subject to exceptions). The new CType statement can be used for explicit casting, and conversion as well (see below). Option Strict requires an explicit type specification on every typed code item, and rules out late binding. Option Strict, when set, implies Option Explicit. As for the latter, the VB team, after all the changes they made to the language, couldn't muster the courage to throw it out of the language and require such a common-sense thing as explicit variable declarations evertime, everywhere, and in any case.

You can still specify options on a per-file basis, but the alternative is to just use the project settings dialog (all options are now supported as compiler arguments as well) for default (settings in a code file will override those). The syntax for Options, when set in a code file, has changed:

Option Explicit On
Option Strict On

Specifiying a type

With Option Strict On, you always need to specify a type (variables do not assume the default type [now Object, formerly Variant]). If you declare several variables with one statement, all variables will take the same type if you only specify the type after the last variable. The following creates three integers:

Dim a, b, c As Integer

That's somewhat similiar to Pascal (and even C, in a sense). Note that you can still declare several variables of different types in one statement:

Dim s As String, i As Integer


Local variables are now only visible in the block in which they are declared. In classic VB, they were visible in the entire procedure, provided that the Dim statement had been "executed" (not stepped over because it was declared in an If block that never got executed). That makes for faster destructions of instances, but note the problem with deterministic finalization when you're using objects. In VB.NET, it is best to declare (and initialize) a variable where it is needed instead of declaring all variables at the top of the procedure. That's the better practice in any language, in my opinion (if the language allows it). Note that the With statement, although it isn't really a control structure, constitutes a scope of its own as well.

What VB.NET does not allow, sensibly, is hiding - local - variables in nested blocks. You can hide a member variable (or a property) by a local variable, but you can't use the same name twice in a procedure. The following illustrates this:

Private Sub Scopy()
    Dim a As Integer
    If IsHellFrozenOver() Then
        Dim a As Integer ' error: no variable hiding in VB.NET
        Dim b As Integer
    End If
    Debug.WriteLine(b)   ' error: strict scoping
End Sub

Static variables

Also note that Static is not supported as a procedure level modifier. That's good, because such a thing would change the meaning of Dim in a procedure. Besides, Static might be confused with the new keyword "Shared" (below) by Java and especially C programmers. Use Static as a variable modifier instead of Dim for local static variables.

Static variables in instance methods are allocated on a per-instance basis, whereas those in Shared methods exist per class (in classic VB, local statics in class methods existed per instance as well).

You cannot use Static variables in procedures that are members of a Structure (see below for Structures, which are the successors of UDTs supporting encapsulation). I speculate that this is for interop reasons (structure layout etc).

Illegal names

Your identifiers are allowed to have illegal names, if you put them in square brackets in the declaration:

Public Property [Class] As String

On the other hand, variable names can now start with an underscore, so there's no need to work arround that. Also, illegal names can be accessed (as opposed to declared) as they are, if it's otherwise clear that they're not used as a keyword:


Conversion, casting, and boxing


You can use the new intrinsic CType statement (functional style), the VB conversion functions, or the "static methods" of the System.Convert "class" to convert integral types. The instance methods you can invoke on primitives only include conversions to Strings. With Option Strict On, there is no implicit narrowing conversion:

' integers have new names (see below)
Dim x As Integer = 32768, y As Short
' three ways to avoid the compiler error, all causing exceptions
y = CType(x, Short)
y = CShort(x)
y = System.Convert.ToInt16(x)

Note that, like in classic VB, conversion does not force anything; it's not like casting (in the true sense) when you're dealing with integral types. You can use it to force compilation if you're sure your numbers won't overflow.

If you really care to convert a boolean to a numeric type (which is mostly unneeded, but here we go), note that VB's functions (such as CInt) yield different results (True becomes -1; for compatibility reasons) than the functions of the System.Convert class as part of the framework (it would yield 1, in accordance with the underlying value of True). The CType statement behaves like CInt (although there are no compatibility concerns here). For that, I suggest avoiding CType whenever possible (I'll discuss legitimate uses below), and use System.Convert. The boolean conversion inconsistencies are due to Microsoft's appeasement policy towards the classic VB establishment, which resulted in a number of rollbacks announced in spring 2001 (during the Beta 1 timeframe), causing a fair amount of debate in the VB community.

In .NET, there are also several ways to convert in a user-defined manner:


By casting, I mean "interpreting as" (whether this reinterpretation is accompanied by run-time checks or not). I do not use the word "cast" in the sense of "forcing whatever it is I want to do by putting a type name between two parens". In civilized languages, of course, "reinterpretation" can only mean "narrow the formal type to the actual type", not "hey, let's look at memory in a different way".

With regard to reference types, use either CType or DirectCast for casting. However, the VB.NET compiler isn't like a C compiler: you can't cast any reference to any other. The types must be related in some way (like casting a reference declared as a base class to a reference declared as a class derived from it). You can cast to interfaces, but note that, differently from classic VB, it's not necessary if the implementing procedures are public (see below), and you happen to know the object's class. If a cast failes at runtime, you get a System.InvalidCastException.

Dim o As Object = New Form()
DirectCast(o, IDisposable).Dispose()

The difference between CType and DirectCast is that CType will attempt a conversion to the target type if the cast fails, whereas DirectCast will not. Therefore, DirectCast should have a lesser runtime overhead.

There is not much reason to use CType for casting (instead of DirectCast): While in the case of classes, there may be a conversion from the actual type to the cast's target type (for example, a reference of type System.Object pointing to a CPoint object that has a user-defined conversion to class CVector), this situation is not handled by the CType statement: In VB.NET, user-defined conversions (defined by a C# class) must be explicitly invoked as methods. But anyway, the usual case is to cast from a reference of a certain type to a derived type, and if the actual cast failes (because the object referenced is not of the target type), there won't be a conversion from the base class to the derived class either. This is because conversions between types in an "Is A" relationship (in other words, related by generalization/specialization) don't make sense. For example, a Person cannot convert itself to an Employee because the extra information needed for instantiating an Employee is, naturally, not available, whereas, going the other way, an Employee already is a Person. Likewise, there can not be conversions to interfaces: as interfaces are abstract reference types, there are no instances of them as such, and a conversion is simply unthinkable.

VB.NET does not offer a "dynamic-cast" statement, such as C#'s "as" (which returns null if the cast fails instead of throwing an exception). Such a thing can be useful for accessing optional, weakly-typed data, such as the Tag property in controls.

Now, very occasionally, you need to reinterpret a simple value as a different type, without any conversion whatsoever. In VB.NET, this is the case when it comes to treating an integer value as an enum (see below for type-safe enums). For this, the only choice is CType, because DirectCast only works on references. But, because this is a real cast (although a value cast and not a reference cast) and not a conversion, I think that DirectCast should be available for this rarely needed scenario. But then again, there there may be a conversion involved (for example, if the enum's underlying type is different), so CType isn't all wrong. Sometimes you'll also use CType when you pass an enum to an overloaded method, and overload resolution is ambiguous (because of implicit conversion to System.Int32 and System.Enum).

Boxing and unboxing

In addition to conversions and casting, there is boxing, which means to treat a value type as a reference type by copying it to an implicitly defined wrapper structure allocated on the heap, and assigning the wrapper's address to an Object reference. The counterpart of this operation is called unboxing. Boxing duplicates data and adds indirection; unboxing removes indirection, but again copies the data. I'll talk about this below.

You can use both CType and DirectCast for unboxing operations. CType will first try to unbox the value, and failing that, will attempt a conversion. DirectCast will simply unbox the value, and failing that, throw an exception. For unboxing operations, using CType can make sense (unlike above):

Dim o As Object, d As Double, i As Integer
d = 12.5
o = d                      ' boxing (see below)
i = CType(o, Integer)      ' unboxing plus conversion
i = DirectCast(o, Integer) ' unboxing plus type mismatch

Keep in mind that a DirectCast on a boxed value only succeeds if the boxed value is exactly of the type that you specify, even if there is an implicit conversion to the target type:

Dim o As Object = 5 ' boxed Integer
Dim d As Double = DirectCast(o, Double) ' exception

To understand why this doesn't work, consider the following, roughly equivalent, C code:

int i = 5;
void * p = &i;
double d = *((double*)p) /* bad */

Since p doesn't point to a double, the code will venture into neverland. The difference is that VB.NET will catch the error at runtime. So using CType for unboxing is useful if you don't know which numeric type is boxed, but are confident the boxed value will fit into, say, a Double.

The type system

Object vs. Variant

Variants are not supported. The default data type is System.Object (Object was an intrinsic type in VB5/6, but it ranked under (or better below) the Variant type in classic VB's type hiearchy; Variants could hold object references as well as other types). Now, Object is the base type; all other types (reference as well as value types) are compatible with it. Value types achieve that compatibility by deriving from System.ValueType, which derives from Object and overrides certain methods. Every value type is implemented by a structure, a mirroring reference type (a class) of which ultimately derives from Object. Object allows (as did Variant) a dull form of polymorphism and late binding. When you assign a value type to a variable dimmed as Object, there's something going on called "boxing". Value types support methods as well, so they're objects, in a sense. You got all that? Well, it takes a while to get used to all that, and I'll expand these lines further down the page. Let's recap some of classic VB's complications first, and compare them to the new ways:

Variants could hold a number of limbo values like Empty, Missing (in a sense), Null, and Error. I don't count Nothing amoung them because it denoted (and still does) a special state of a reference variable, that state being quite descreet. It also helps to remember that Nothing was (and is) the default value of any reference: it was not unique to the Variant type). Here is what happened to the limbo values:

A unified type system

On a general note, we now have some sort of a unified type system, with System.Object being the root type. From System.Object class derives System.ValueType, which overrides certain methods (such as ToString and Equals). All value types derive directly from this class, except enums, which derive from System.Enum, which derives from System.ValueType. While System.ValueType and System.Enum are themselves classes, all instances of types derived from them (such as System.Int32, System.DateTime, System.Weekday, or System.Guid) are just plain values when the type is used directly, but are class objects when boxed (I'll try to clarify this below).

Languages define key operators (such as assignment and equality) differently for value types. And of course, memory management is different: value types live on the stack (or are embedded in another object). And for the programmer, using a value type means that every variable of that type equates to one instance of that type. (Whereas reference type variables represent simply a reference; they're initialized to Nothing, which means "not referencing an object").

Bridging the gap between reference types and value types, which is necessary if you want to assign value type instance (in .NET terminology, this is simply called a "value") to a reference of type System.Object, works by copying the value into a wrapper object living on the garbage-collected heap, and assigning its address to the reference. In other words, the reference used (that is, a variable of a reference type) is then really a pointer to the boxed value. This is what's called "boxing". Every structure (which is, again, coincident with a "value" in .NET) can thus be boxed (technically, there exists an unnamed "boxed type" that is transparently defined for every value type; it's those boxed types that participate in the type hierarchy).

So, can you choose whether to use the boxed version of, say, System.Int32 or treat it like a value? Not directly. You can get an instance of the boxed type by assigning a value (of the unboxed type) to a reference of a type that the boxed type derives from. Let's consider the type hierarchy in this example:

Type Variable denotes
System.Object Reference
   System.ValueType Reference
      System.Int32 (Integer) Value

As for the boxed type System.Int32, you'll never see it, but when you box a value, the reference (of type System.Object or System.ValueType) refers to an instance of the boxed type. You cannot have a reference of type System.Int32 (this would imply special syntax for distinguishing it from an unboxed value, which was not deemed suitable for either VB.NET or C#; but in managed C++, you do have more choices with regard to boxing).

Both boxing and unboxing operations result in making a copy. Boxing makes a copy on the heap, whereas unboxing copies the values to whereever the variable lives (on the stack, mostly). This is true in both strict and permissive VB.NET (Option Strict On or Off) and in C# as well. This is important, because referring to stack-based instances from a reference with a longer lifetime would not mix well with high-level languages.

Private o1 As Object
Private o2 As Object

Public Sub Boxy()
    Dim i As Integer
    o1 = i
    o2 = i
    Console.WriteLine(o1 Is o2) ' False
End Sub

Of course, this isn't an exact copy: the boxed value is nested in a self-describing object, which is necessary to support a few virtual methods (the ones defined in System.Object, and potentially in System.ValueType and System.Enum; but not those methods in user-defined structures, which can't be virtual anyway), and provide runtime type information needed for subsequent unboxing.

How boxed values behave

If you pass the reference (of type System.Object, System.ValueType, System.Enum, or an interface type) arround (that is, assign it to a ByVal parameter, return it, assign it to a variable, field or property), it's just about references: the boxed value that the reference points to is not copied.

In other words, while both boxing and unboxing operations result in copying a value, once that value is boxed, no copies are made by copying the reference(s).

This is contrary to what I have claimed earlier in this article (I alledged that in VB.NET, "assigning one to another will make a copy of the boxed value"). I honestly don't remember what made me believe that - it could be a leftover from Beta 2, or a misinterpretation of the documentation, but definitely a great deal of sloppiness on my part.

Dim a, b As Object, i As Integer
i = 1	' check out i in the locals window for comparison
a = 5	' check out a in the locals window to see the "box"
b = a	' copying the reference
Console.WriteLine(a Is b) ' True
b = 4	' changing b to point somewhere else; a is still 5

You don't normally get to see that System.Object is a reference type when it points to boxed values, however, because both boxing an unboxing operations result in copying the value. You can't take advantage of reference semantics, because you can only manipulate the value by an explicit unboxing operation (which again means making a copy). That is because boxing reduces the type information available (as stated above, there are no references of boxed types that you could use access members defined on the value type).

Mutating boxed values

What about a boxed Structure (a value type that supports encapsulation; roughly it's the equivalent of a UDT; see below), and manipulating one of its members? Though luck. For one, Option Strict On rules out late binding (as is always the case in C#):

Structure TPerson

    Public Name As String

    Public Sub New(ByVal sName) As String)
        Name = sName
    End Sub

    Public Sub SetName(ByVal sName As String)
        Name = sName
    End Sub

End Structure

' ...

Dim o As Object, p As TPerson
o = p
o.Name = "Bob" ' error: no late binding

You might think you could cast o to the TPerson type like this:

DirectCast(o, TPerson).Name = "Sue" ' error: can't assign to this

Value type structure members cannot be assigned to when they result from a value, not a variable (see below for properties that yield a structure). We seem to have more luck if we use a setter function for changing the person's name. The compiler doesn't mind when a method works on a temporary copy (it can't know that a method is a mutator, even though it might look like a property setter):

DirectCast(o, TPerson).SetName("Sue") ' compiles, but doesn't work

When you check the object referred to by o again, you'll see that the boxed structure's Name property hasn't been changed. Now, in order to manipulate the structure, you need to unbox it to a variable. This means making another copy of the value:

Dim t As TPerson = DirectCast(o, TPerson)
t.Name = "Lisa"

It's clear from the Dim statement that this gets you nowhere. A value type will ultimately behave like a value type. You can stuff things into Object variables to pass them arround, or store them in a weakly-typed collection, but you don't get reference semantics here ("changing one changes the other because there isn't any other").

The gist of this story is that (when implementing a type) you should choose value types over reference types only when things are supposed to be immutable (that is, no instance methods that change the value exist). This way, any change to a value can only be made through an assignment to the value as a whole. I'm going to expand a bit on these issues, but meat of the issue is that complications only arise if the the boxed type in question is mutable, and an attempt is made to mutate it by calling a member. As long as boxed values are constant (by interface), or when merely assigning a different value to a reference as a whole, it doesn't matter so much whether and when a copy is made.

Value types and late binding

The next catch here is for assembler-type programmers who prefer to operate in weak-type mode. If care to turn off type protection (Option Strict Off), you can invoke late-bound methods (runtime resolution):

' using Option Strict Off
Dim p As New TPerson("W")
Dim o As Object = p
o.Name = "Dubya"
Console.WriteLine(o.Name) ' you guessed it

The reference o refers to a boxed copy of p. With "permissive semantics" (ETC) ruling, the assignment works directly on the boxed (!) value. If I try to do the same thing using reflection (just for comparison), the boxed value will not be changed - for now, I don't really understand why.

While talking about late binding, let's note that this works only with references of type System.Object (as an aside, this is even true for late-bound reference types). As noted above, value types do not directly derive from System.Object, instead, there is the System.ValueType class (and in the case of enums, the System.Enum class) in between. Now, you can assign a value to reference of type System.ValueType as well:

Dim pt As TPoint: pt.X = 3: pt.Y = 5
Dim vt1, vt2 As System.ValueType
vt1 = pt
vt2 = vt1
Console.WriteLine(vt1 Is vt2) ' True

So multiple references can again point to the same boxed value. But you can't get the benefits of reference semantics, because late binding is not an option with references of types other than System.Object:

' using Option Strict Off
vt1.X = 7 ' error: no late binding with System.ValueType

In essence, this means you can enable true reference semantics (changing a boxed value by using a copied reference) for value types in VB.NET, by using System.Object references of for late-bound method calls or property assignments. But System.ValueType references do not allow this.

These behaviours aren't so obvious, so here we find many reasons to keep Option Strict On, and just forget about late binding: the questions whether the reference or the thing referred to is copied, or whether or not and how a boxed value is mutable can be safely disregarded when Option Strict is On, because changing the boxed value requires explicit unboxing anyway, then.

Boxing in the dark

As if implicit boxing wasn't "don't-bother-me-with-details" enough, there is behind-the-scenes boxing in situations where you don't expect it:

Friend Structure Point

    Public X As Int32
    Public Y As Int32

    Public Sub New(ByVal x As Int32, ByVal y As Int32)
        Me.X = x
        Me.Y = y
    End Sub

    Public Sub MoveBy(ByVal dx As Int32, ByVal dy As Int32)
        X += dx
        Y += dy
    End Sub

    Public Sub MoveBy(ByVal dx As Int16, ByVal dy As Int16)
        X += dx
        Y += dy
    End Sub

    Public Overrides Function ToString() As String
        Return "{" & X & "." & Y & "}"
    End Function

End Structure

So lets create such a point, and try to move it. Note that we do not box the Point structure here; we intend to invoke the MoveBy method directly on the unboxed, stack-based value:

Option Strict Off

Public Sub Main(ByVal args() As String)

    Dim pt As New Point(3, 5)

    Dim dx As Object = 10
    Dim dy As Object = 10

    pt.MoveBy(dx, dy)


End Sub

Because none of the MoveBy method overloads takes System.Object, the overload resolution would fail if Option Strict was in force. But with late binding, the method picking can be done at run time, so the compile succeeds. When the code runs, the right method (MoveBy(Int32, Int32)) is selected and invoked, because the boxed argument values are both of that type. Why, then, does the code print the following:


Because the Point structure is boxed, and the method is called on the boxed value. Why is the value boxed? Because overload resolution as well as the call happen in a helper function, which works for all types, taking a System.Object parameter. Thus, the boxing.

There are three ways the VB.NET team could address this:

  1. Use copy semantics, that is, copy back the boxed value after the mutator call has returned. Since VB.NET does this in other places as well, why not here?
  2. Inline at least the call. Leave the method resolution in the helper routine, but invoke the method at the original place (Sub Main, in our example). This has a code size penalty, however.
  3. Make the helper function, as well as certain methods in the System.Reflection namespace generic, and use a ByRef parameter for the value. However, this is the most work-expensive solution.

Another unexpected place where boxing happens is when you point a delegate to an instance method of a value type (the boxed copy can be found through the delegate's target property; the original struct, of course, is unaffected).

Other value type oddities

If you think that keywords like New or Nothing have no place for value types, you'll be surprised:

Dim i As Integer = New Integer()
i = Nothing

Does this make sense? No. The first statement just calls Integer's default initializer, which VB.NET calls anyway (in C#, locals are not initialized to a sensible default value, but there's a definite assignment requirement). But then again, a value-type variable declaration always creates a new instance, not just a reference. So the whole thing is about initialization, not memory allocation. This syntax has a place with structures, which can now have constructors with parameters (see below for more on structures).

The second statement really is a lie. You can't set a value to Nothing - a value type instance always holds a value of its type. The VB.NET compiler will just translate this to an assignment of the default value. To me, syntax like this is evidence of an "anything goes" attitude to coding on part of the language designers. Actually, the docs suggest a somewhat different interpretation of "Nothing" (something along the lines of a "universal default value"), further muddying the waters. The fact that Nothing implicitly converts to anything complicates its use in calls of overloaded methods more than necessary.

Everything is an object - or looks like it

You'll also notice that value types, in addition to being assignment-compatible with the Object type due to boxing (although not fully behaving like reference types then), are now objects (in a more generic sense) as well. Of course everything is an object in the sense that we might call every named storage location an object (think of C). But in .NET, every instance is an object in so far as methods ("active" members) and thus some encapsulation are supported. Below you'll read more on structures (which support methods and a few other things). But at this point, let's note that even on primitives, such as an Integer, you can invoke a method using the familiar OO syntax:

Dim i As Integer = 123

Note that for value types, methods calls do not generally pass through a vtable (a data structure holding pointers to member functions). Rather, they're like global functions which are selected at compile time (there's no runtime lookup unless values are boxed). Note however, that methods inherited from the base reference types (like ToString of System.Object) are indeed called through a vtable even when called on an unboxed value, which requires temporary boxing, unless they are overridden in the value type. While the value's type and thus the actual method to call are known at compile time, and the "this" pointer that the inherited virtual method takes as its zeroth argument could well point to an unboxed value, the method implementation might rely on having a self-describing object arround, which takes up more storage. Thus, the boxing. The latter means that it's a good idea to provide custom ToString, Equals, and GetHashCode methods (moreover, the implementation of Equals in System.Value type compares all fields bitwise, which might not be necessary for a given type).

Likewise, value types are not inheritable. Inheritance would, to make sense, imply assignment compatibility (since a variable of a value type allocates/reserves a given amount of storage, an instance of a "derived" value type, which might have appended a field, couldn't be copied to that location). For that, polymorphism is not an option with value types, and neither are inheritance nor method overridibility. Note however, that types like System.ValueType, System.Enum, System.Int32, etc., are in an inheritance relationship, so that seems to challenge what I've just said. But these are the boxed types, which are reference types and thus subject to type specialization. That there is a pair of value type and boxed type for things like integers and enums is mandated by the concept of a unified type system. Often, the difference is transparent, but it's always clear when a value is boxed and when it's not.

Taking the above into consideration, it's clear that value types aren't "fat" objects. In fact, they're quite efficient. They're - mostly - allocated on the stack, and the methods you call are just global functions in disguise. The fact that we can write "variable.method()" instead of "function(variable)" is a syntax detail - the compiler will sort it out. Specifically, there is no boxing going on just because you call a method on a value - unless it's a method inherited (ie., not overridden) from System.Object, as discussed above.

Specific types


Strings are now reference types derived from System.Object. You have methods on the string type, so they're objects, too. The String class is NotInheritable (sealed in C#, final in Java).

Strings are immutable, that means, once created, their memory buffer cannot be changed. String's methods create new String objects as needed, so you can "manipulate" strings in spite of that. Also, you can still use the concatenation operator, for example, although you end up creating a new string. Strings are made of Unicode characters.

The fact that strings are immutable, along with their being reference types can be a bit confusing. All methods that manipulate strings, as well as all string functions or string statements (like Mid), return new Strings. When you assign one String variable to another, you're not copying the string, you're only copying the reference. But the result of any string manipulation only affects the references they are assigned to.

For example, when you change a string with the Mid statement, the newly created string object will be assigned to the source variable only (note that Mid's first parameter is actually ByRef, so that the reference can be changed to point to the new String object).

So practically, it is like value semantics. The explanation lies in String's immutability: When you assign to the string variable (even with the new compound assignment operators [&=]), it'll point to another string; you won't copy a value over the original string.

That String is a reference type makes sense (performance). But because strings are immutable, you get neither the benefits nor the drawbacks of reference semantics.

All in all, Strings are still easy, and due to their immutability, they behave like value types (or Copy-On-Write types, if you will). Keep in mind that the Mid statement, which could efficiently manipulate a string in classic VB, now creates a new string, so it's not nearly as efficient anymore.

There is a StringBuilder class in the framework that allows for faster concatenation (or other manipulation) of Strings. It's not interchangeable with Strings, but it has constructors that take Strings as well as a ToString method. You can access characters by using the (Default) Chars property. For copying a String, however, there is no advantage from using StringBuilder.

Note that fixed-length strings are not supported. In structures, you can easily use strings if they're passed by a mere pointer; otherwise you need to either fake the structure by allocating a blob on the native heap, or use attributes (additional meta data that describes the type) to enforce passing your string as an array of characters in the structure. It's possible to create a buffer of a given length using one of String's constructors, but although strings are immutable, you can still assign the variable to another string (literal or variable), ending up with a buffer of a different length - a fixed-length string would not let you do that, so there is some bug potential here. Actually, there is a compatibility class that lets you create fixed-length strings, but it won't really work with Option Strict On.

Strings, being reference types, are initialized to Nothing. However, a Nothing string reference compares equal to an empty string ("") when using the "=" operator (note that the Compare method works differently; see below). If you initialize the string to "", it compares equal to Nothing with the "=" operator as well. But if you use the "Is" operator, which compares references, results are different. If want to know if a string has chars in it, you should test for "" using the comparison operator. Otherwise, compare strings with the Shared Compare method (but it works differently from the comparison operator in that Nothing and "" are not considered equal). Try the following code to study the behaviour:

Dim a As String = Nothing, b As String = ""
If String.Compare(a, b) = 0 Then Stop		' False
If a = b Then Stop				' True
If a Is b Then Stop				' False

Dim s1 As String = ""
If String.Compare(s1, "") = 0 Then Stop		' True
If String.Compare(s1, Nothing) = 0 Then Stop	' False
If s1 = "" Then Stop				' True
If s1 Is "" Then Stop				' True (see below)
If s1 Is Nothing Then Stop			' False

Dim s2 As String = Nothing ' same as default
If String.Compare(s2, "") = 0 Then Stop		' False
If String.Compare(s2, Nothing) = 0 Then Stop	' True
If s2 = "" Then Stop				' True
If s2 Is "" Then Stop				' False
If s2 Is Nothing Then Stop			' True

Note that if if you initialize two string variables with the same character sequence, this might be represented by only one instance, so a reference comparison (Is) will yield true as well. But this is nothing you should rely on.

Also note that, unlike in Beta 1, the "=" operator does not compare references. The behaviour described here is for strings only.

The vbNullString constant can be found in the Compatibility namespace only. Its value is Nothing, which is kind of the same as the Null pointer (WCHAR *p = 0;) that vbNullString was (in spite of the IDE's showing an empty string). In case you don't want to use the compatibility baggage, just use Nothing when calling an API.


There's a new type, Char, that represents exactly one double-byte character (there's is also [still] Byte; both are unsigned). Have a look at the System.Text.Encoding class for string and byte array conversions.

There are still character constants, but the "vb" prefix has been droppped. You can find them in the ControlChars class.


Integers have changed in size:

Again, the reason behind this is a cosmetic one: most other languages define Integers (int etc.) as 32 bits wide or the native machine size, which today is mostly the same. If you'll work with VB.NET and say, C#, Java or 32-bit C++, it will eventually be one less difference to watch out for; but, of course, if you'll use VB.NET and VB5/6 side by side, it's vice versa. They could have called 64-bit interger "Large" or something. But they really should have made this change with the first 32-bit version of VB. Anyway, they could not have made a perfect decision here. The Upgrade Wizard will take care of it, however. If you decide to search-and-replace, make sure you make the Integer-to-Short changes first.

The .NET type system offers unsigned versions of integers (UInt16, UInt32, UInt64). You can declare them (but there is no nick name), but you cannot perform arithmetic operations on them; you need to invoke the operators defined on these types explicitly.

Fixed-point numbers

Currency (64 bits wide) is not supported. Instead, you can use Decimal (a 96 bits wide signed integer internally, scaled by a factor of 10) for constant accuracy calculations.


True is now defined as 1 instead of -1, in case you care. You're in trouble if you relied on the underlying value, although using the CInt function (or the implicit conversion in ETC-mode) will convert True to -1, as in VB6. The .NET framework's conversion functions, however, behave differently. So it's best to just forget about about internal representations. So if you really need to convert things, write your own functions. Just don't rely on any underlying values. Or even conversion functions that come with VB.NET, for that matter. It's just too big a mess.


Dates are not stored as Doubles internally. If you relied on the underlying value, you're in trouble. Now, they are stored as 64-bit integer numbers. This should speed things up a little. "Date" is an alias for System.DateTime. There's also an interesting TimeSpan class. If you want to time your program, look for "Ticks".

Type declaration shortcuts

The DefType statements are no longer supported. You could use them with Option Explicit Off (very bad) to create variables of a given type on the fly.

There are new type characters. The old ones - ! # % & $ - are still arround. Here are the new ones: S (Short), I (Integer), L (Long), F (Single [Float]), R (Double [Real]), D (Decimal), C (Char). You use them with literals. In particular "A" is a String; the following is a Char: "A"C. This is important when passing "A" to an overloaded function (below). The difference between the old cryptic type declaration characters and the new ones is that the new ones can be added to literals only, not to variables.

Type information

As an alternative to the TypeOf operator, you can use the GetType method (inherited from Object) to get extended type information (you get an instance of the Type class). There's also the GetType statement, which returns a Type object as well, given a type.


Bitwise and illogical

Yes. For Beta 1, the VB team decided to give us C-style logical operators. This would have broken a lot of code, but there is much to be said in favor of having strongly typed operators, boolean and bitwise strictly separate. This is especially true in VB, with it's historically weak type protection - which is still possible to have (also note that Option Strict Off is the default setting for VB projects in Visual Studio). The point is, if an operator expects a boolean operand, implicit type conversions aren't all that bad. I'm not arguing in favor of evil type coercion (ETC), but not having overloaded operators is an extra safety layer. Not to mention the simplicity and consistency of it. Or the clarity, both for the original coder and the code maintainer - the operator would always do what it says it does. Overloading is OK if the aim is only to accept different, but related data types while performing the same operation. It's acceptable for procedures, but not for operations so common that a language offers a special keyword (or token) token known as an operator. None of these requirements are met by the overloaded operators And, Or, Xor, and Not.

So the VB team have backpaddled. We don't really (well, you'll see) have boolean-only operators, neither bitwise-only operators. Instead, we'll continue to use the old overloaded versions. Turning Option Strict On certainly helps: you can't mix and match integers with booleans, and if you're disciplined enough, you're safe. By the way, And and Or keep not being short-circuited.

So what's my gripe here? Well, it's about missing the opportunity for a better design, at a time when backwards compatibity has to be sacrificed anyway. And it does not really help with migrating existing code. If coders had followed best practices, boolean logic could still compile just fine like it did in Beta 1 (if coders hadn't followed best practices, they're in greater trouble anyway). And bitwise operator usage could be clearly flagged as an error (or the conversion wizard could work things out easily).

Even if Option Strict is applied against old code, there is no guarantee that the resulting conversion errors are fixed the right way:

' code that VB6 allowed
Dim i As Integer, j As Integer, f As Boolean
' ...
f = i And j ' What is the intention here? There is more than
            ' one way to get this code past the compiler!

The biggest problem I have here is the lack of direction in the VB team. What they sell as a compromise between "moving forward" and backwards compatibility is really one-sided decision in favor of the latter. In Beta 1, it was vice versa. Who's in charge?

Short circuiting

Wow. What am I missing? Yes. We do have new operators. Strongly typed. Boolean-only. Not overloaded. Short-circuited. We only have two of them, instead of four as in Beta 1. One of them is called "OrElse":

Do Until x Is Nothing OrElse x.Value = 0

Native English speakers say that this reminds them of their parents' telling them "Clean up your room, or else ...". The other operator is called "AndAlso" (note the negation of the logical expression due to the use of "While" instead of "Until"):

Do While Not x Is Nothing AndAlso x.Value <> 0

So the first test is evaluated, and then, "also", the second one? I'm sure I'm not the only one who finds this name, uhm, uninituitive. Really, folks, this name is complete nonsense. There have been many discussions in VB.NET newsgroups over this, and very few people actually defended this choice of term. It would take five minutes to replace this keyword by something sensible, like "BoolAnd", but MS is just being too stubborn.

Operator precedence

The rules of operator precedence haven't been changed a lot compared to classic VB (given the rollback on boolean operators). "AndAlso" shares the rank with "And", "OrElse" has the same precedence as "Or".

The drawback is that bitwise tests continue to be awkward and require parens. In Beta 1, things were so nice. No conversions, everything was perfectly clear:

' Beta 1
If a BitAnd b = b And x BitAnd y = y Then

More (less) logic

The Eqv and Imp operators are not supported. Both operators were bitwise, but with True being -1 (or any defined value for that matter), were used for boolean operations as well.

Eqv compared two expressions for logical equivalence. The workarround for boolean operands is to simply compare both operands for equality. For numeric operands, use the one's complement operator on the Xored operands:

a = (b = c)      ' boolean eqv
a = Not(b Xor c) ' bitwise eqv

Imp tested for logical implication (inference). If the first operand was False, the result would always be True; if the first operand was True, the result was true only if the second one was also True. You could use this to test whether an inference was valid: the first operand stood for the validity of your premises, the second one for the validity of the conclusion. Here's how you code Imp in VB.NET (for both boolean and bitwise operands, respectively):

a = Not b Or c   ' boolean and bitwise imp

Coumpound assignment

Optionally, you can use compound assignment operators for many math operations as well as for string concatenation. Unfortunately, this does not work with logical or bitwise operators.

i += 1		' increment integer
s &= "Lisa"	' append string

Control structures

Structured exception handling

In addition to traditional error traps (such as On Error GoTo), VB.NET offers structured exception handling of the Try-Catch-Finally style:

Catch e As NotInterestedException
Catch e As NotAtHomeException
Catch e As Exception
	Debug.Write("Unexpected exception: ")
End Try

Exceptions are managed by classes that inherit from System.Exception instead of error numbers. You can catch different kinds of exceptions polymorphically (as in the last Catch clause), or set up specific traps (beware to place the most specific exception classes topmost).

There is also the When keyword, which allows you to test for extra conditions in the catch clause. It is syntactic sugar for an If/Then, but your exception handler code may come out cleaner:

	ary(i) = Value
Catch e As IndexOutOfRangeException When i > UBound(ary)
	' resize array in large chunks, but only
	' if index exceeds upper bound
	ReDim Preserve ary(i + 10) 
	ary(i) = Value
Catch e As IndexOutOfRangeException
	Throw e
End Try

Note that in the When clause, you can also access the identifier of the exception caught (the When clause is only evaluated if the Catch matches the type of the exception that has occurred).

The Finally clause is guaranteed to execute, regardless of whether there was an exception. It runs if jump out of the entire procedure with an Exit statement, even if you throw an exception of your own in a catch clause. The latter is useful if you want to pass on caught or new exceptions to the caller, but want to put others to rest, and still need to clean up (you can even throw an exception within the Try clause, then catch it, pass it on with a Throw Statement, and still rely on the Finally clause):

	If userInfo.Validate() = False Then
		' if InternetSecurityException inherits from
		' SecurityException, it'll be caught in
 		' the first Catch clause (polymorphism)
		Throw New InternetSecurityException()
	End If
	' assume connect could throw a SecurityException
	' (see the first Catch clause)
Catch e As SecurityException
	' modify caught exception, using an inner exception
	Throw New InternetSecurityException(e)
Catch e As NullReferenceException
	' pass on caught exception
	Debug.WriteLine("Some bozo passed a null ref!")
	Throw e
Catch e As NetworkException
	' NetworkExceptions die here
	MessageBox.Show("Bad net!")
	' this will always execute
End Try

The Catch clauses are optional if you use a Finally clause; vice versa, the Finally clause is optional if you have one or more Catch clauses.

GoSub and GoTo

Pre-procedural constructs like Gosub/Return, On ... GoTo, and On ... GoSub are gone. Note that "On Error GoTo" still works.

While loops

The Wend keyword has been changed to a more consistent "End While". As Do-Loop is more powerful, the While construct isn't necessary and should have been stripped from the language entirely.

Working with procedures


Parentheses are optional still if no arguments are passed. When arguments are passed, the parens are required, whether the result of a function call is assigned to a variable or not; it does not matter how many parameters are provided. The new rules are stricter, but they are easier to remember and more in sync with other languages.

ByVal default

Procedure parameters are now by default passed ByVal. That's good because it turns off side effects, and it's more efficient for reference types. Structures can now be passed by value as well (below).

The caller cannot override the passing convention determined by the callee.

Passing properties by reference

If you pass a read/write property by reference, the callee can write to that property by assigning to the ByRef parameter (you're passing a pointer to the property in the same way as a pointer to a variable, thus changes are reflected):

' the proc:
Sub ResetAge(ByRef iAge As Short)
	iAge = 25
End Sub

' create object, pass the property ByRef
Dim person As New CPerson("Susan", 50)
Debug.WriteLine(person.Age)	' 25

However, if the property is ReadOnly, you do not get an exception. There really should be at least a compiler warning.

Parameter arrays

Parameter arrays are passed ByVal; such parameters must have the ByVal keyword. Note that arrays are now reference types, so you actually pass a reference of the array anyway. But changes the callee makes are reflected only if you pass a pre-declared array; if you pass variables, these are copied into the array first, so any changes the callee makes are not visible in the caller:

Sub Test()
    Dim a() As String = {"Monday", "Tuesday"}
    Dim s1 As String = "Monday", s2 As String = "Tuesday"
    ChangeArray(s1, s2)	' different in VB6!
End Sub
Sub ChangeArray(ParamArray ByVal days() As String)
    days(0) = "Lunes"
    days(1) = "Martes"
End Sub

You can now declare a parameter array of any type (in classic VB, you could only use the Variant type). You can also pass such an array on to another procedure that expects a parameter array, where it will be treated the same way as in the first function (this didn't work in VB5/6 because the required Variant type would nest the array into the first and only element of the second function's parameter array). You can call a procedure with a ParamArray parameter in two ways: either pass values that are implicitly convertible to the ParamArray's element type, or pass an array (provided it's compatible: see below for array covariance). Here's an example:

Sub Foo(ParamArray ByVal days() As String)
End Sub

Sub Bar(ParamArray ByVal days() As String)
    ' days is the same array as in Foo,
    ' not a nested array
End Sub

This is good news. It means you can write a wrapper for a procedure that has a ParamArray parameter, or use such a routine for tail-recursive programming.

Optional parameters

When declaring an optional parameter, a default value must be assigned. It's OK to use the default initialization value, which is why this requirement is nonsense. But at least we get to keep optional arguments. In a C++ Managed Extension, they're illegal altogether.

Structure types are illegal for optional arguments. This is not different from classic VB, although it's for different reasons: in classic VB, structures had to be passed by reference, and optional arguments implemented as pointers to complex objects add more overhead (as the callee will implicitly dereference them, there needs to be a temporary object); now in VB.NET, optional arguments must have a default value initalized to a constant expression known at compile-time.

Return keyword

Exit a procedure with Return (as an alternative to Exit Sub or Exit Function). Assign a return value with the same keyword (as an alternative to Function = Value) when exiting.

Procedure overloading

Constructors, Subs, Functions and Properties can be overloaded. If there already is a member with the same name in a base class, you get a compiler warning if you don't use the new "Overloads" modifier. If you overload members in the same class, you don't get a warning, but you can still apply the "Overloads" modifier. However, the latter is illegal for constructors (the reasoning for that seems to be that the base class's constructors aren't inherited [well, not exposed] and thus you can't overload a base class constructor anyway; but you can overload constructors defined in the same class, so this is the first hint that "Overloads" means something ..., well, more on this further down this page.

The procedures must differ by the number of arguments, or, if this number is the same, by the type of at least one parameter. The return type does not matter:

Public Overloads Property Item(ByVal index As Integer) As CPerson
' ...
Public Overloads Property Item(ByVal sKey As String) As CPerson
' ...

Beware that the "Overloads" keyword is overloaded itself: It implies shadowing when you apply it to a method that has the same signature as a method declared in a base class (if the base class method is overloaded, this shadows only the matching version).

Using constants in calls

The compiler now hard-copies the value of constants (these are implicitly Shared/static fields) as well as that of Enum members into calling code (unlike in classic VB, where constan objects where evaluated at run time). This can cause compatibility problems if an application uses constants defined in a different component.


You can think of delegates as objects that encapsulate pointers to one or more procedures. Delegates are reference types. The event model is based on delegates, too. I discuss them in another topic.

Structures (UDTs)

The Type keyword has been replaced by the new Structure keyword. Structures are really objects, since you can define methods, properties, and events on them. They can even implement interfaces. They do not support inheritance, however. The key characteristic is that structures are value types.

Think of structures as lightweight objects, but take care how you use them: when you cast to an implemented interface, there is a boxing operating going on under the hood. Structure methods are not implemented via a vtable (as structures aren't inheritable), so method lookup is more efficient than it is in classes, but not if you call through an interface.

Structures can be used as return types or arguments for class members everywhere (not just in public library classes as in VB6). Be aware of the value semantics when trying to assign to the fields of a structure that is returned by a property - you can't, the compiler catches this "assign-to-copy" mistake.

Here's a structure:

Structure TPerson
	Private m_Name As String
	Public Age As Short
	Public Sub New(ByVal sName As String)
		m_Name = sName
	End Sub
	Public Property Name As String
			Return m_Name
		End Get
			m_Name = Value
		End Set
	End Property
	Public Sub ShowName()
	End Sub
End Structure

Initializing a structure

As structures are value types, every declaration of it creates an instance. Still, you'll see the "New" keyword used with structures, occassionally. This isn't so elegant, from a syntax design point of view, but it's practical if you don't need a named variable:

Call CreatePageFault(New TPageFaultInfo(Color.Blue))

Also note that a structure can have a custom, parameterized constructor (like the above example), and using "New" is the only way to invoke it. Whether or not you provide a constructor, the compiler will always emit a parameterless standard constructor that sets all the fields to their default values. This compiler-provided constructor is not a default constructor, as in classes, which means you cannot define your own parameterless constructor.

It's unfortunate that you can't define your own parameterless constructor, because sensible initialization with no arguments is just as useful for structures as it is for classes. This is especially true if your structure contains an array that needs to be marshalled to unmanaged code as an array of fixed length (see below). Even if you provide your own parametered constructor, VB.NET does not care whether you actually call it. C# is somewhat stricter in this respect (the compiler requires either an assignment or a constructor call before use). The reason why structures cannot have parameterless constructors is performance (think of allocating and initializing an array of structures, as opposed to an array or mere references).

Implementing interfaces

A structure can implement an interface. Let's do it the wrong way:

Interface IRenameable

    Sub SetNewName(ByVal sNewName As String)

End Interface

Structure TPerson
        Implements IRenameable

    Private m_Name As String

    Public Sub New(ByVal sName As String)
        m_Name = sName
    End Sub
    Public ReadOnly Property Name() As String
            Return m_Name
        End Get
    End Property

    Private Sub SetNewName(ByVal sNewName As String) _
        Implements IRenameable.SetNewName
        m_Name = sNewName
    End Sub

End Structure

To call the interface, we need a reference of the interface type, and assign the value to it. This implies boxing:

Sub Test()

    Dim p1 As New TPerson("Lisa")
    Dim r As IRenameable = p1

    ChangeName(r, "Rosa")
    Console.WriteLine(p1.Name) ' Lisa

    Dim p2 As TPerson = DirectCast(r, TPerson)
    Console.WriteLine(p2.Name) ' Rosa

End Sub

Sub ChangeName(ByVal r As IRenameable, ByVal sNewName As String)
End Sub

You'll notice that we assign the structure to a reference of the interface type. This results in boxing the value, which means creating a new copy of it. You cannot avoid this. The Sub "ChangeName" is without effect on the original value (although interfaces are a reference types: it's the boxing that creates the copy; needless to say, even a ByRef parameter of the interface type in the "ChangeName" Sub wouldn't help).

Note that the implementing method could be public as well. If that was so, we wouldn't need to box the structure to call the method. However, this does not solve the problem of the interface method working on temporary copy: the point of interfaces is to be ignorant of the actual type of an object. Practically, it's a reference of the interface type that is used (regardless of the visibility of the implementing member) to call the method, so boxing values cannot be avoided. Note that this is not a problem if an interface does not stipulate mutators (in other words, if it only specifies readonly properties or methods that do not affect the object's state).

Interop and structure implementation

Internally, structures are not necessarily stored as contiguous memory blocks. However, you can mark a structure with a so-called marshalling attribute (see below for .NET custom attributes) to ensure it's passed to a Windows API or a COM component as one contiguous block:

<StructLayout(LayoutKind.Sequential)> _
Structure TPageFaultInfo
    ' ...
End Structure

The sequential layout is the default (other layouts are automatic layout and explicit layout, the latter allowing you to simulate a union).

Another issue are inline arrays of a fixed length. Since arrays are now reference types, an array in a structure is just a reference. If unmanaged code expects a fixed-length array in a structure, you need to apply an attribute as well:

' struct _TBlob {
'     BYTE ab[10];
' };

<StructLayout(LayoutKind.Sequential)> _
Structure TBlob
    <MarshalAs(UnmanagedType.ByValArray, SizeConst := 10)> _
    Public ab() As Byte
End Structure

Note that this attribute only applies to the marshalled structure: you still need to dimension the array properly.

If the Windows API defines a structure that has a pointer to another structure, you need to copy that other structure to the unmanaged heap (using Marshal.StructureToPointer), and assign the result to the field, which must be of type System.IntPtr. Altough the UnmanagedType enumeration used with the MarshalAs attribute has a member "LPStruct", it cannot be used with in a structure (it's only for parameters). Here's a wrapper:

Public Function StructureToHGlobal(ByVal struct As ValueType) As IntPtr
	Dim size As Integer = Marshal.SizeOf(struct)
	Dim ptr As IntPtr = Marshal.AllocHGlobal(size)
	Marshal.StructureToPtr(struct, ptr, False)
	Return ptr
End Function

If the Windows API defines a structure that has a pointer to an array of other structures, you've got similiar problems. You can take the address of the array's first element (using Marshal.UnsafeAddressOfPinnedElement), but you have to "pin" the array first, using a GCHandle. Another option is to allocate space for the array on the unmanaged heap, and copy each element there:

Public Function ArrayToHGlobal(ByVal array As Array) As IntPtr
	Dim ptr As IntPtr = IntPtr.Zero
	If array.Length > 0 Then
		Dim first As Object = array.GetValue(0)
		Dim size As Integer = Marshal.SizeOf(first)
		Dim total As Integer = size * array.Length
		ptr = Marshal.AllocHGlobal(total)
		Dim i As Integer
		Dim ptrTmp As IntPtr = ptr
		For i = 0 To array.Length - 1
			Dim obj As Object = array.GetValue(i)
			Marshal.StructureToPtr(obj, ptrTmp, False)
			ptrTmp = New IntPtr(ptrTmp.ToInt32() + size)
		Next i
	End If
	Return ptr
End Function

Remember to free the memory with Marshal.FreeHGlobal.

Assignment changes

You can no longer assign one structure to another one of a different type with the (abandoned) LSet statement. In a strictly typed language, that's just common sense. In response to criticism on that particular change, Microsoft suggested in a paper that one use "RtlCopyMemory" (sic) to achieve that effect. It is as though they couldn't make up their minds about type safety. Moreover, since structures aren't always stored as contiguous blocks and might be moved arround by the garbage collector (although they are regularly allocated on the stack, this is not so specified), Microsoft's advice sounds doubtful.

However, the Marshal class (in System.Runtime.InteropServices) lets you copy a structure to the unmanaged heap, and copy it back on a variable of a different type. But that's a different operation, whereas LSet was more like a real cast on a non-scalar type (which was unique to classic VB, as far as I know: even in C, you need to use memcpy to achieve such a thing).

Passing by value

Finally, you can now pass structures by value. Note that ByVal is the default now.


Usage and instantiation

The Set statement is illegal. Yes, that's right. They could have left it in as optional, but no Sir, VB does not express the concept of reference semantics anymore when you assign to object variables. But objects are the normal way of organizing data now, so it saves some typing. But try to remember that, when you assign to an object variable, it's only a reference, not the thing itself.

If you declare an object reference with As New, you create the object (in classic VB, this would make the object creatable by accessing its members, but delay instantiation). If you set such a reference to Nothing, it won't reinstantiate if you call members (you will get an exception). Using As New in VB6 was inefficient (because of the implicit "If x Is Nothing Then Set x = New CThing") as well as dangerous (because a Nothing reference should give you an error when you try to party on it). Another reason for the change is that, with the loss of deterministic finalization (see below), you don't want to create objects willy-nilly. One more good reason for the change: classes can now have parameterized constructors - delayed object creation would make the compiler's task even more demanding, and result in more code bloat. Simimiliarly, the new, more predictable, object creation semantics also allow you to use the "WithEvents" modifier in an "As New" reference declaration.

Classes now have one or more constructors, called "Sub New"; when you initialize the instance, you can feed data to the constructor's parameters:

Class CThing
    Public Sub New(ByVal sMaterial As String,
                   ByVal clr As System.Drawing.Color,
                   ByVal weight As Integer)
    ' ...
    End Sub
End Class

' use the class:

Dim thg As CThing
thg = New CThing("Metal", Color.Black, 500)

You can also combine both steps (instantiation and initialization):

Dim thg As CThing = New CThing("Metal", "black", 500)

Does that look like Java? We don't need to be as verbose, though:

Dim thg As New CThing("Metal", "black", 500)

Fields can be initialized inline, as an alternative to using constructor code (see below for the ReadOnly modifier):

Public Class CTimeStamp

    Public ReadOnly Created As Date = Date.Now

End Class

In classic VB, classes had an Instancing property, which is no longer supported. Here are the new equivalents:

Refering to the current instance

You can still use the Me keyword to refer to the instance that an instance method or property has been invoked on. This preserves dynamic method lookup for Overridable (virtual) methods and properties.

To restrict virtual method lookup down to the superclass, use the MyBase keyword. In other words, this refers to implementation of a virtual method in the most derived class where it is defined, but no further down than the immediate superclass.

VB.NET offers a third possibity: restricting a method's virtualness to the current class. This makes it possible to call a virtual method's implementation in the current class, even if it has been overridden by a more derived class (it's as though the method was not overridden). For this effect, use the MyClass keyword.

Accessibility modifiers

Private now means "accessible from the context in which something (a method, a field) is declared", not "private to the instance". For example, if both A and B are objects of the class CFred, A can talk to B's private variables. This can be useful (think of shoveling elements arround in a doubly linked list), but we're also loosing a way of protecting ourselves (as class designers) from accessing data in another object. Don't even try to find this one in the documentation.

There is a new modifier, Protected, as an alternative to Public and Private. It means that a derived class can access elements of the superclass.

Shared members

Use the new Shared keyword to create members that aren't associated with a class instance. These members are accessed by the class name ("YourClass.YourSharedMember"). Use them to keep an instance count, for example. Instance members can access Shared members, but not vice versa. If you use a module, it's implicitly a class that has only Shared members (use that for global functions). Shared means "static" in C and Java.

An illogical syntax hack (copied from Java) is that you can access Shared members using a reference variable of the class type. This sacrifices readability for nothing, and it leads to questionable design choices, like implementing what should really be an instance property as a Shared member, just because what is returned should be the same for all instances (which could be achieved otherwise nevertheless: using instance properties delegating to a private shared field). In this respect, note that when someone uses your class in C#, he'll have to refer to your static member via the class name, because that language doesn't allow him to access statics via an instance reference. Even more importantly, calling a shared method via an instance reference suggests that polymorphism is at work (invoking overridden instance methods according to the actual type pointed to), which might actually not be the case if it's really a shared method (how do you tell them apart when looking at method invocation code?) that has really just been shadowed in a derived class: the results depend on the context, because the call is resolved only upon the type information available at compile time. The meat of the issue is that when a reference variable is used to invoke a Shared method, it appears as if it was somehow relevant (to the action performed) what reference points to: but this is simply not the case (it's even irrelevant if the reference is Nothing). So use shared members sparingly, and never refer to them via a reference.

Const and ReadOnly fields

Constants can now be public in any class. As their value is determined at compile time, all constants are implicitly Shared.

An alternative to constants are ReadOnly fields. These are in fact write-once members: you can initialize them from any expression, even if it's not known at compile time. However, you can only set the value in an inline initialization statement, or in the constructor. ReadOnly fields can, unlike constants, be allocated on a per-instance basis. The value of ReadOnly fields is not "hard-copied" into calling code; therefore, the initialization value of such a field can be changed without breaking (binary) compatibility for clients.


The syntax for properties has changed. It means less typing, but also less flexibility. For example, you cannot expose property procedures with mixed visibility (like Friend Let and Public Get). But it's easier if you have several parameters. It's now irrelevant whether the property is of a reference type or of a value type - assignment works the same, because the Set statement has been dropped. Default properties are now marked with a keyword. Also note the Value keyword, which is provided automatically by the IDE and stands for the value that the client assigns (you can use a different variable name if you want to avoid using an existing name [though there shouldn't be a naming conflict]):

Public Default Property Item(ByVal index As Integer) As Object
		Return m_col(index)
	End Get
	Set(ByVal Value As Object)
		m_col(index) = Value
	End Set
End Property

Parameterless default properties are not supported, however. That's ironic because now that we have a decent way of marking default properties, their use is limited to indexer properties like "Item". It has to do with the removal of the Set keyword. A default property will be an indexer in C# (in case you're insterested).

If you create a Public variable at the class level, it'll be a public field (no more property procedures behing the scenes as in COM).

That we can't define properties with mixed visibility anymore is partially compensated for by the fact that "Private" now means class instances can access private data of other instances of the same class (above).

If you pass a read/write property to a procedure by reference, the callee can update the property (see above).


There are no more "default interfaces" (which were necessary because COM didn't allow classes to expose methods, so classic VB faked them by providing a default interface for every class). Interfaces and classes are strictly separate in VB.NET.

If you implement an interface, you still have to provide a procedure for every member (even if you don't add any code to the method body). But you can use any arbitrary name for these procedures, since they must be added an Implements clause of their own, allowing you to use one procedure to implement several members of one (or again, several) interface(s) the class implements (it's similiar to event handlers, in that respect). Furthermore, this explicit interface implementation helps avoid naming conflicts. Here's an example:

Private Sub Foo() Implements ICocktail.Bar, IPiano.Bar

End Sub

You can also make the implementing procedure public, allowing clients to access it without casting to the interface type (just using an "As SomeClass" variable). This is what you get in Java, for example, but it isn't the point of interfaces.

Using private members to implement an interface has several advantages. For one, you can use a DLL-private interface with private member types that otherwise couldn't be exposed in a public class in that DLL. Secondly, a class can support generic operations (like indexed element access on a collection by a sort algorithm) without exposing generic types (like System.Object) directly to the user.

Interfaces can inherit from any other interface (unlike in VB6, where IUnknown was every interface's ancestor). Interfaces can also inherit from multiple interfaces (unlike in COM). (To be clear on this, multiple interface inheritance is different from implementing multiple interfaces in a class.)


Events are based on delegates (see that topic for more details). You can sink several events in one handler. As a class designer, you can still use the old "Event/RaiseEvent" style syntax, but you can more easily create an event pattern by separating delegate and event declaration. The following two approaches work the same, but the latter can save you some typing when creating an event pattern:

' classic way
Public Event FireOpened(ByVal sender As Object, _
                        ByVal e As FireEventArgs)
Public Event FireCeased(ByVal sender As Object, _
                        ByVal e As FireEventArgs)

' new way (for event patterns)
Public Delegate Sub FireEventHandler(ByVal sender As Object, _
                                     ByVal e As FireEventArgs)
Public Event FireOpened As FireEventHandler
Public Event FireCeased As FireEventHandler

An "Event" is like a property that returns a reference to a multicast delegate. However, manipulation of this property is restricted: the Event keyword allows only comining and uncombining operations - you can't assign a delegate directly. This means two things: The delegate object is only referenced by one event at a time (combining and uncombining operations always return a new delegate, because delegates are immutable): this isolates other events that users may sink to the same event handler. Secondly, users of your object can only add (or remove) one event handling procedure to (or from) the delegate at a time: this protects other listeners from being accidentally disconnected. In short, delegates' immutability protects senders, the Event keyword protects listeners.

Implementation inheritance

Classes can now extend other classes by using the new Inherits keyword. This works for any class that isn't marked as NotInheritable. Inheritance greatly eases control development. You cannot inherit from multiple base classes, however (but you can implement any number of interfaces).

Classes inherit any interfaces implemented by the base class, as well as their base class implementation. However, this implementation can be overriden (if the methods are so marked).

Constructors are inherited, but not exposed (their visibility is reduced to "protected" if they're public in the base class). You can call the base's constructor using the construct "MyBase.New". If you don't add a constructor, a parameterless one is provided for you by VB, which will call the parameterless constructor of the base class. If the base class doesn't have parameterless constructors, you need to call one of them first explicitly in from a constructor (this way, the class designer can ensure that one of the base class's constructors is always called when an object comes to live).

Overriding, Overloading and Shadowing

You might wonder what overloading has to do with inheritance in particular: aren't two methods that differ not by their name but by their arguments different methods? Isn't the choice between overriding and shadowing made independently for each overload? And why would you have that choice anyway?

In short, while I don't know of a language doesn't give the base class designer any choice (for example, instance methods in Java default to be virtual unless they're declared final, whereas in C++, only methods marked virtual methods may be overridden while others may only be shadowed: but both languages leave that choice entirely to the designer of the base class), other languages give the inheritor a say as well: In C#, the only methods you can override are those marked virtual, but you can shadow them as well if you like. In all of the languages mentioned, adding a method that differs by signature is a method just as different as one with another name, and that's that.

In VB.NET, you have all the choices you have in C#, plus another one. And this is where overloading comes into play. You can shadow all versions of a method defined in a base class (which is the default behaviour), or you can shadow just the one having the same name and signature. If a method is "Overridable" (virtual), you can also override it. Here's how it works:

You can only override members that are marked with the "Overridable" modifier. But if you don't use the "Overrides" keyword in the derived class, you won't override the method. Instead, you get a compiler warning to the effect that the method shadows a member in the base class: take it seriously. Shadowing breaks the chain of inheritance, and throws polymorphism to the dogs: which version is invoked then depends on the context, that is, the formal type of the reference used to invoke the method. To override, the method must be overridable, and you must use the "Overrides" keyword.

You can also overload members defined in the base class by using a different signature (see above for procedure overloading in general). If you don't apply the new "Overloads" keyword to the method, you get a compiler warning to the effect that you should use said keyword: otherwise, the new method will shadow by name, and won't overload. For shadowing by name, use the "Shadows" keyword explicitly: This will make sure that your method is still chosen by the compiler under the same circumstances, even if the base class later introduces an overloaded version with a better match. If you use "Overloads", you'll hide by signature only.

Likewise, when you add a method with the same signature as one already declared in the base class, the compiler tells you (as a mere warning) that you're shadowing a method in the base class. Using the "Overloads" keyword reduces the shadowing effect from "shadowing by name" to "shadowing by (name and) signature". You can also apply the "Shadows" modifier to hide by name (that is, hiding all versions defined in the base class). If you do not specify anything, the method is assumed to shadow by name ("Shadows").

This all seems very strange (because, again, shadowing will kill polymorphism). Another issue is the semantics of "Overloads": shadowing by signature doesn't seem to be exactly the same as overloading, but then as you may have guessed by now, VB.NET has a different mechanism (e.g., different from C#) for picking a given version out of a list of overloaded procedures. The compiler looks for the best match from alike-named methods across the entire inheritance hierarchy; it doesn't simply pick the best match from the most derived class possible:

// C#
using System;

class A {

    public void Foo(int i){


class B : A {

    public void Foo(double d){


class Test {

    public static void Main(string[] args){
        B bb = new B();
        bb.Foo(1); // actual int; calls B.Foo(double)
        bb.Foo(1.1); // actual double; calls B.Foo(double)

Option Strict On

Imports System

Class A

    Public Sub Foo(ByVal i As Integer)
    End Sub

End Class

Class B
    Inherits A

    Public Overloads Sub Foo(ByVal d As Double)
    End Sub

End Class

Module Test

    Public Sub Main(ByVal args() As String)
        Dim bb As B = New B()
        bb.Foo(1) ' actual Integer; calls A.Foo(Integer)
        bb.Foo(1.1) ' actual Double; calls B.Foo(Double)
    End Sub

End Module

If it wasn't for the "Overloads" keyword on B.Foo(Double), the VB.NET version would behave exactly like the C# version, but different reasons: the C# compiler is content with the best match in the most derived class, whereas VB.NET wouldn't even see the shadowed method. So "Overloads" does affect method resolution.

That shadowing (by name) is the default is a surprising choice at first. However, it ensures that method binding by the compiler (according to overloading rules) remains consistent if the base class later introduces a method that matches the formal types of the arguments provided more closely. However, you still have the choice of using "Overloads", allowing the new base class method to be chosen. The latter will only exclude methods that have the same name and signature from the selection process. And at this the point, it might be worth reflecting that both shadowing and overloading are method selection mechanisms that take place at compile time: in this sense, overloading is related to shadowing.

To complicate matters more, the "Overloads" keywords hides by signature only if the method is not an override, as shown below (both "Overloads" and "Overrides" often appear together in object browsers).

"Overloads" is somewhat comparable to C#'s "new" (which shadows overloadable members [i.e., methods and indexers] by signature, and shadows everything else by name). One difference I've found is that "Overloads" can not be used to only change a method's return type ("new" can). A more substantial difference is that "Overloads" does not always shadow by signature (i.e., if the method is an override). Yet another difference is that "Overloads" does not shadow other, non-overloadable, declared entities.

"Overloads" means: leave everything with a different signature alone, and either shadow by signature or override (in the presence of a legal "Overrides" keyword).

The VB.NET design choice of defaulting to shadowing avoids the common Java trap of getting different results depending on whether a field is accessed directly or via a method (using a reference of the base class type in both cases), if the field has been shadowed while the method has been overridden. Furthermore, it prevents programmers from accidentally defining potential overrides (by choosing a method name that is later used in subsequent version of the base class). Note that both of these advantages are also available through C#'s default of hiding by signature.

However, allowing these things to happen implicitly does indeed seem to be a feature inspired by the Java philosophy: Java lets you implicitly hide base class fields, while methods are implicitly overridden. At least there are compiler warnings in VB.NET, but I'd much rather treat the absence of the relevant keywords as an error. Computer languages are for humans, not for computers.

Here is an example that sums it up so far:

' note: the "Public" modifiers have been omitted for brevity

Class Base                   Class Derived : Inherits Base

  Sub A()                      Sub A()
  End Sub                      ' warning: this shadows by name;
  Sub A(ByVal x As Integer)    ' "Shadows" makes the default explicit
  End Sub                      End Sub

  Sub B()                      Shadows Sub B()
  End Sub                      ' this shadows all versions of "B"
  Sub B(ByVal x As Integer)    ' (shadowing by name)
  End Sub                      End Sub

  Overridable Sub C()          Sub C()
  End Sub                      ' this still shadows by name;
                               ' use "Overrides" to override
                               End Sub

  Overridable Sub D()          Overloads Sub D()
  End Sub                      ' this shadows by signature (not yet an
                               ' overload); use "Overrides" to override
                               End Sub

  Overridable Sub F()          Overrides Sub F()
  End Sub                      ' this overrides
                               End Sub

  Sub G()                      Sub G(ByVal x As Integer)
  End Sub                      ' warning: this shadows by name;
                               ' you should use "Overloads"
                               End Sub

  Sub H()                      Overloads Sub H(ByVal x As Integer)
  End Sub                      ' this overloads, potentially
                               ' shadowing by signature only
                               End Sub

  Sub I()                      Overloads Sub I()
  End Sub                      ' This isn't really an overload (same
                               ' signatures). Rather, for now shadowing
                               ' by signature is implied. There is no
                               ' compiler warning for that dubious use
                               ' the keyword, but then, the keyword in
                               ' itself is doubtful in the first place,
                               ' and what compiler would opine on that?
                               End Sub

End Class                    End Class

With regard to "Overloads", as hinted above, there are interesting complications in the following example:

Option Strict On

Imports System

Class A

    Public Sub Foo(ByVal i As Integer)
    End Sub

    Public Sub Bar(ByVal i As Integer)
    End Sub

End Class

Class B
    Inherits A

    Public Overridable Overloads Sub Foo(ByVal d As Double)
    End Sub

    Public Overridable Overloads Sub Bar(ByVal d As Double)
    End Sub

End Class

Class C
    Inherits B

    Public Overrides Overloads Sub Foo(ByVal d As Double)
    End Sub

    ' BC40003: this shadows an overloadable member
    Public Overrides Sub Bar(ByVal d As Double) 
        MyBase.Bar(d) ' but we still override
    End Sub

End Class

Public Module M

    Public Sub Main(ByVal args() As String)
        Dim cc As C = New C()
        cc.Foo(1)   ' A.Foo(Integer) is closest match
        cc.Foo(1.1) ' C.Foo(Double) is closest match
        cc.Bar(1)   ' A.Bar(Integer) is not available: widens to Double
        cc.Bar(1.1) ' C.Bar(Double) is the only match
        Dim bb As B = cc
        bb.Bar(1.1) ' Virtually calls B/C .Bar(Double)
    End Sub

End Module

The output:






Using an As B reference in the last call makes it clearer that C.Bar(Double) is an override, although using the MyBase keyword in C.Bar(Double) (without compile error) is sufficient for that claim.

In essence, this example shows that overriding and shadowing by name not mutually exclusive (in C.Bar(Double). In C.Foo(Double), the "Overloads" keywords really means what it says (it does not hide by signature, it overloads A.Foo(Integer)).

You can not, though, apply the "Shadows" keyword to C.Bar(Double) - in other words, the compiler defaults to a behaviour that you cannot specify explicitly. Or kind of - C.Bar(Double) actually shadows by logical-Not-Signature, if you think about it.

I'd like to stress that the real purpose of "Shadows" is to keep a derived class compatible with clients when later the base class introduces a new member that causes an overloading conflict. It is a versioning tool, and thus should be used very cautiously.


Objects are (in a general sense) managed by references because classes are reference types. If a language enforces reference semantics on classes (like VB, Java, C#, but not C++), the task is to keep object lifetime in sync with the references. That task may fall on the programmer, or the language or the execution engine may help out. In COM, there is the familiar reference counting, done on a per-object basis, which means that the object keeps the the number of references using it in an integer variable while the clients are responsible for telling the object to increment or decrement the reference count by calling methods like AddRef and Release. This makes COM awkward to use in C++, whereas in VB5/6, the language handled the refcounting behind the scenes (both the object's bookkeeping chores and the clients' calls of AddRef and Release). In Java, and now in the .NET platform, references aren't counted, by rather traced in a global manner by a beast called garbage collector, which runs every once in a while and checks for objects that aren't referenced. This makes it possible to resolve circular references without any work on part of the programmer.

The problem with the garbage collector is that it must continually check for unreferenced objects. It will be given low priority in order to save system ressources. But that means that a client cannot count on an object's being destroyed (and, more importantly, its ressources' being freed) immediately after releasing the last reference. In other words, objects are not finalized (destroyed and cleaned up) in a deterministic (predictable) manner.

That is a problem when an object uses scarce ressources (like database connections, file handles, graphical objects) that are to be kept during its entire lifetime. A clean-up method (like Dispose) is of little help when when several clients of the object depend on these ressources to be available. Another problem is that you cannot rely on your objects' being destroyed in a predictable order. I discuss some solutions in a different topic.


When accessing an Enum, you need to fully qualify it, that is, type in the enum name, followed by a dot and the constant name. The reason is type safety (two enums using the same name for a constant can be ambiguous). However, when you import an enum (using the Imports statement), you only need to name the constants as long as the names are not ambiguous. Enum members whose name starts with an underscore are no longer hidden.

All enumerated types inherit from System.Enum. An enum is like a one-member structure with shared public fields of its own type. This implies type safety - an enum is not simply a group of constants or bit masks; you can't assign any integer value to an enum variable (casting notwithstanding); however, you can assign an enum value to an integer.

You can initialize enum members with the values of members of another enum without any explicit conversions. This is useful if you need to map values between different enums (for example, if some values are not suitable for your needs). Note that the values of enums are hard copied into calling code (just like constants).

Fun with enums

The System.Enum class has a method that tests whether a given value is among the defined values. There is a special exception (IllegalEnumArgumentException) that you raise in case the test fails. In the same way, you can obtain the symbolic names of the fields if, for example, you want to display them in a combo box by calling the ToString() method. To put all the values that the enum defines into an array, use this code:

Dim days As System.Array = System.Enum.GetValues(GetType(Weekday))

You can persist enum values as strings, and then parse the names to get back the values (you can parse either the member names or translate the underlying values).

The fact that you can use reflection on an enum allows design-time tools to display its member name in an appropriate property editor. With the System.Flags custom attribute (see below for .NET custom attributes), you can specify that an enum's members are to be combined bitwise (a property editor can offer choices accordingly). Besides that, the Flags attribute is a structured way of documenting the enum's intended use.

An enum's underlying type is Integer by default. You can specify a different underlying type like this:

Private Enum TestMode As Short
End Enum

Data structures: Arrays and Collections

Arrays: bad news

Let's discuss a break that that touches the language's soul. VB, as a high-level idiom among programming languages, has always let the programmer decide where to start counting. But no more (at least not directly, there is some weird workarround). Now we start at zero (the same goes for collections, too). It's a shame that VB looses that easy flexibility. By messing with arrays, Microsoft has once again shown its ineptitude of understanding the language's heritage. Of course, C masochists will tell you it's natural or mandated by the alledged "equivalence of arrays and pointers" to start indexing at zero. Of course, anything else would just confuse them to no end when they play pointer math. But they'd just as happily start counting at negative 273 if K&R had told them so. Enough of that.

As a consequence, note the following changes:

Dim a(10) still creates an array of eleven elements (0 through 10), not of 10 like in Beta 1. You specify the upper bound, and the lower bound is always 0. This syntax is inconsistent with other crippled languages that enforce zero-based arrays. While it preserves compatibility with VB6 (to whatever small and pointless extent, since the real problem is losing user-defined bounds), the whole design is a big mess. Why can't we at least have the option of writing "Dim a(0 To 10)"?

Of course, VB.NET provides some hackish way to create non-zero-based arrays, using some compatibility classes and turning Option Strict Off. Oh yes, you can also use the Array class (which is interchangeable with arrays) to create non-zero-based arrays, but it requires that you create two zero-based arrays to set the lower bound and the initial length (works similiar to using COM SAFEARRAYS in C++). In any case, the ease is gone, so it's not a great consolation, if any.

Arrays: good news

You cannot use Redim to create an array that has not already been declared. This avoids a bug that Option Explicit was designed to solve: you thought you resized an existing array, but if you mistyped, you really created a new one. Now Option Explicit applies to arrays, too.

Arrays can be single dimensional, multi dimensional, and even ragged (asymetric). Furthermore, we now have a special initialization systax for arrays, although it's a bit messy (see the "Collections" topic for more details).

Another thing: arrays are now reference types. This comes from Java. The class System.Array is the base class for all arrays, be it arrays of values or arrays of references. If an array contains references, there can be several types involved in an array:

Dim bears() As CBear             ' null reference to array of CBear
Dim grizzlies(9) As CGrizzly     ' ref. to array of CGrizzlyBear
bears = grizzlies                ' assign (array covariance) 
bears(0) = New CRedEyedGrizzly() ' OK
bears(1) = New CBattleShip()     ' Comile error
bears(2) = New CTeddyBear()      ' ArrayTypeMismatchException

As you can see, compile time type checking is limited: the formal type of the array reference rules out storing a battle ship in an array of bears, but there is no way to know at compile time that the array pointed to by the bears reference is an array of grizzly bears. Bears related to grizzlies can be stored in it, but not other bears that are not grizzlies. This error is only discovered at run time. While arrays offer generic but type-safe data collection functionality for any type (they're like template instantiations), the facility of array covariance is the ultimate limit of that type safety.

Note that array covariance does not apply to arrays of value types: these arrays store their values directly, so the values are not boxed. You can't assign such an array to a reference of type System.Object(), because that would be too much overhead "behind the scenes". The following utitlity function does it for you:

Public Function BoxArrayValues(ByVal ar As System.Array) As Object()
   Dim o, ao(ar.Length - 1) As Object, i As Integer = 0
   For Each o In ar
      ao(i) = o ' boxing if "ar" stores values
      i += 1
   Next o
   Return ao
End Function


An equivalents of VBA.Collection still exists. There are many new collection classes in addition to it (as discussed in "Collections"). If the new collections use indices, they start at zero, which is most awkward to use in Basic (see the C statement for comparison):

For i = 0 To col.Count - 1
Next i

for(i = 0; i < col.GetCount(); i++){

It might be a good idea to add a couple of properties to collections, so as to iterate in a decent way (if For-Next is required for some reason):

For i = col.LowerBound To col.UpperBound

Next i

The Collection class, however, uses one-based indexing for compatibility reasons, so this is an inconsistency to watch out for.

Free threading

VB.NET supports free threading, as opposed to COM's single apartment threading model that VB6 had. To start a thread, get a new instance of the Thread class (in System.Threading), tell it about a method to run (via a ThreadStart delegate), and call the Start method:

Dim bc As New CBeanCounter()
Dim t As New Thread(AddressOf bc.CountWebSites)

' ... 
Class CBeanCounter
	Public Sub CountWebSites()
		Dim ws As CWebSite, n As Long
		For Each ws In System.Web.Sites
			n += 1
		Next ws
	End Sub
End Class

There are a lot of issues that come with free threading, such as notifications, data exchange, thread synchronization, thread-safe libraries, and so on, that I can't cover in a few paragraphs, and probably won't cover at this site. Here are a few pointers:

Pointers and other hacks

Getting at addresses

Undocumented functions like VarPtr, StrPtr, and ObjPtr have been removed from the language. Also note that the AddressOf operator does not return a function's address anymore; rather, you can only use it with delegates (as discussed under "Delegates"). If you need to pass a function's address to an API, you can declare the respective parameter as a delegate type. This will also work for function pointers that are to be stored in a structure field (note that the VB5/6 hack-arround for that is necessary anymore: AddressOf can be used freely in any [compatible!] assingment context now).

If you really need a pointer to something other than a function, you can use the static (Shared) methods of the Marshal class (in System.Runtime.InteropServices). It's rather interesting what you can do there. Also, have a look at the GCHandle class.

API calls

Declare statements still function as they used to, and there is a new clause that lets you specify the character set used (Ansi, Unicode, or Auto). In addtion to that, there is the DllImport attribute (see below for attributes): it can't do much more than a Declare, but it's worth learning, as it is used in other .NET languages (like C#).

You can no longer pass pointers to structures to APIs using the Any type. APIs that can take more than one structure type for an argument can be declared by overloaded Declare statements. Just declare the parameter with the given type(s) (ByRef mostly). You can also use the StructureToPtr method of the Marshal class.

When a parameter in a Declare statement is declared as ByRef, you cannot override this by using ByVal in the caller (note that ByRef parameters in Declare statements used to behave differently than those in regulare procedures in classic VB!).

Reflection and attributes

Reflection is a powerful technique that allows an application to obtain meta information about types, methods and other code items at runtime. It's possible to invoke methods or properties dynamically by name, instantiate classes by name, load assemblies at run time, access private members, and much more. It's helpful when the aim is to specify a fact only once (aspect-oriented programming); although the price is losing compile-time checking of types and member names.


If you know IL (the intermediate format that the code is compiled to), you can create dynamic assemblies at run time, and provide method implementations with IL OP-codes. Dynamic assemblies can be persisted as well. This is useful if you need highly optimized code in situations where the "optimal" implementation is discovered at run time (like loop unrolling depending on the size of a collection, etc.), or as an alternative to either VSA or CodeDom code compilation (writing an interpreter that generates IL).

Custom attributes

But in .NET, the meta info contained in binaries is not restricted to predefined aspects of code elements. You can create what are called Attributes (by deriving a class from System.Attribute):

Public Class AuthorAttribute
        Inherits Attribute

    Private m_Author As String
    Private m_Tested As Boolean

    Public Sub New(ByVal sAuthor As String)
        m_Author = sAuthor
    End Sub

    Public ReadOnly Property Author() As String
            Return m_Author
        End Get
    End Property
    Public  Property Tested() As Boolean
            Return m_Tested
        End Get
        Set(ByVal newValue As Boolean)
            m_Tested = newValue
        End Set
    End Property

End Class

This defines an attribute that you can use to decorate classes, methods, or fields (there are ways to restrict attribute usage - through an attribute applied against your attribute class itself). You apply an attribute by writing a constructor invocation statememt in angle brackets before an entity (on the same logical line; note that the conventional "Attribute" postfix is optional here). You can initialize any read/write property of the attribute class using VB's named argument syntax, even if it's not part of the constructor:

<Author("Lisa", Tested := True)> _
Public Sub ReverseEarthRotation()
    ' ...
End Sub 

The compiler/linker will place that information in the binary. At runtime, you can retrieve the attribute using reflection; then, an instance of your attribute class will be created:

    Dim m As MethodInfo, a As Attribute, as As AuthorAttribute
    m = Me.GetType().GetMethod("ReverseEarthRotation")
    a = m.GetCustomAttribute(m, GetType(AuthorAttribute))
    aa = DirectCast(a, AuthorAttribute)
Catch AmbiguousMatchException
    ' method overloaded, or attribute applied more than once
Catch SecurityException
    ' no permission for reflection
End Try

Attributes have many uses:

Code organization


Projects can reference other projects as well. If you use a COM component, a wrapper DLL will be created for you. Referencing a library does not make its types available immediately; rather, you need to import its namespaces into the code file (but there is a list of global imports in the project's properties dialog). If you don't import namespaces, you need to fully qualify a path of namespaces (e.g., System.Runtime.InteropServices.Marshal).


Namespaces are a new feature; basicly, they help you organize code. One assembly (roughly: EXE or DLL) can contain several namespaces. But the same namespace may occur in different assembies as well. When using a namespace, you only need to specify the namespace name, not the library file. Namespaces can be nested as well, and you can declare the same namespace within the same declaration scope multiple times. If you create a new project, VB.NET will create a default namespace for the entire project, so you don't need to worry about this right away.


The Imports statement makes the types defined in a namespace available for unqualified use. The same applies to nested types and Shared (static) type members, if a type is imported. The following table compares how VB.NET, Java, and C# deal with qualified names:

Feature VB.NET Java (1.5) C#
Import single type: ' not supported import java.util.ArrayList; // not supported
Import multiple types: Imports System.Collections import java.util.*; using System.Collections;
Import nested types and static members: Imports System.Math import static java.lang.Math; // not supported

With Imports, you can also specify a namespace alias:

Imports WinForms = System.Windows.Forms

Private m_Button As WinForms.Button ' As System.Windows.Forms.Button

New forms engine

Forms are very different. There is no longer a global variable for every form class that lets you access properties and controls from anywhere. You cannot access a form just by the class name. Control references added to a form using the forms designer now have Friend access (by default). It's preferable to change that to Private: then, there's no excuse for not building a decent object model arround the application. All forms inherit from System.Windows.Forms.Form (in classic VB that was the only case for single, one-level inheritance). What's more, you can inherit from existing forms (as well as controls) and enforce a consistant corporate look across the application much more easily.

But most form and control specific code is not upgradable. The new stuff is better, but your code is broken. There many subtle differences that make it hard to migrate. However, the overall concepts are not so radically different that you needed a complete redesign. Much of the UI logic can be translated in a more or less straightforward way, but it's not a task that you want to leave to the Upgrade Wizard. You might prefer to do it yourself.

More control over forms

VB.NET still comes with a forms designer, and creating forms is just as easy as in classic VB - drop a control, set some properties, and write event handlers. However, you don't have to use the designer. It works like in VJ++, the designer generates initialization code as you click the form together. Everything you design ends up as code.

This means that you can get rid of the designer altogether, if you want to. Personally, that's what I do most of the time. If you have UI logic code of any significance, you can write the form yourself as well. With the new docking and anchoring capabilities of controls, the initialization syntax for objects, and easier event sinking, that's easier than you might think.

However, if you modify the designer-generated code, there's a change the designer won't recognize it when you try to reopen the form in it. It turns out the designer isn't as smart as the compiler. So either use the designer for everything, or don't use it at all; don't mix the two techniques.

Form objects and underlying windows

Like in VB6, form objects can be used without creating the underlying Win32 window data. It seems that forms behave a little more civilized - that is, when you set properties, the form doesn't load itself out of a sudden. It appears that if you call Close (not Hide), the underlying data structures are freed (but call Dispose to really clean up).

The Load event is back since Beta 2, and it's preferable for most initializing code that needs to have the Windows data structures properly initialized. Use the constructor instead of the Initialize event.

The framework provides the NativeWindow class, which encapsulates just the window procedure and the window handle. For low-level operations, you can hook up a form on such an object. Still, the Form class allows you to do a lot more than before, such as overriding the WndProc method (which calls the window procedure) in a safe way.

What's gone, what's different

Collecting and identifying controls

Control arrays are not supported. Every form has a collection to keep controls, and you can still dynamically add or remove them at runtime. Use the new delegate feature to add event handlers. In the event handler, to find out who triggered an event, compare the sender reference variable with a class level reference to your controls, or just use the name property. The Tag property has been butchered, but there is some of extender mechanism to fake it. Using the forms designer you can only assign strings to it; although its type is "Object". You can also create a custom control by inheritance, and add a special property to it that you can test against, or that can hold any special data (like references to objects in you app).

The Name property has reappeared in Beta 2. The Name property really gets/sets the name of a reference variable. You wouldn't normally want to know the name of the reference, which you use to talk to the control; the name of a reference variable is conceptually something quite different from the "name" of a control.

Controls for special data

Image controls aren't arround anymore. Maybe you'll be able to purchase something from a third party. You will miss the OLE container control, too. PictureBox is still there, however (it also supports animated GIFs). For leightweight drawing, use the classes from System.Drawing in the OnPaint method.

Windowless controls

You cannot create windowless controls. Labels for example, aren't lightweight anymore.


There is an IComponent interface and a standard implementation class, Component (which is an ancestor of Control, and thus of Form as well). Using classes derived from Component eases disposing of objects, especially if they're shared between applications. This is a good thing, considering that objects aren't finalized predictably.

Design time controls

Somewhat related, IMO, is the concept of design-time-only "controls". They're billed as "components" as well, and it seems this term from the Delphi world is becoming more popular. Don't even think that Microsoft has come to senses: there seem to be even more built-in design-time-only "controls", uhm, 'scuse me, components, so the concept still keeps haunting us with its evil spirits. The term "design-time control" is an oxymoron; it's a concept that lets you configure certain aspects of your app without writing code. I don't think much of it.


Line and Shape controls are not supported. The same goes for the Line, Point, Circle, PSet, and Cls methods. You have to use the graphics classes from the framework for even the simplest things. The upside is that these classes are really powerful for 2D graphical effects.


The new ScaleMode is pixels, not twips. This will make for a great deal of recalculations. The good thing is that controls can now be anchored, docked, or flooded.


Menus and context menus are now separate, although the menu items are the same. The new menu editor is a little better. You can handle the Click events of all menu items in one handler. You can also sink an event when the user moves the mouse over a menu item (in order to display a description in the status bar). It's easy to draw menus yourself.

Clipboard and Drag-and-Drop

Communicating with the Windows clipboard works differently now. The same for drag-and-drop. I have not yet evaluated the new methods, but it seems there's nothing to worry here.


To set a mouse pointer globally (for all controls), you have to set it for every control. The closest replacement for for Screen.MousePointer is to set the mouse pointer for the entire form.


The format of ressource files has changed. The overall ressource model is a little different. For example, you can embed a ressource, such as an icon, in your assembly, and refer to it by name.

Miscellaneous changes

File I/O and Data Access

File I/O

For file I/O use classes from the framework; Open and friends have said goodbye. There is now a new syntax for reading and writing files (so it's once again a language feature), but the fact that the framework provides wrapper classes makes this change seem totally unnecessary. There is no advantage from the new (native) file I/O statements, to put it mildly.

Data access

All data access now is through ADO.NET. RDO, DAO, and Jet can still be used, but they will not work with databound controls. ADO.NET is different from ADO, it has some nice features like disconnected datasets (which are disconnected sets of tables). You can iterate through fields as well as rows using For-Each. The object model has improved over prior ADO versions (for example, transactions and connections are now managed by different classes; recordsets with their plethora of cursor properties have been replaced by datasets, data readers, and command objects). The downside is that the Jet engine was smaller, easier to use for desktop databases, and it gave the user some distinct options to control access permissions.

ADO.NET works with so-called data providers. There is an OLEDB data provider, and an SQL data provider (optimized for SQL Server 7.0). In version 1.1 of the framework, there's also an Oracle data provider. You can download an ODBC data provider as well. Each data provider has its own connection, command, and data reader objects (and more), but you can use a set of interfaces for flexibility.

Data binding is still not optimal, but ADO.NET supports binding disconnected data, which makes data binding acceptable from a performance point of view. The DataGrid control is interesting, but with ADO.NET, you have lots of control over displaying data. You cannot bind controls to RDO or DAO sources.

Internet development

Consolidating project types

WebClasses, DHTML projects, and ActiveX documents have been abandoned in favour of ASP.NET. Given that these concepts overlapped somewhat, and these kinds of projects haven't been killers anyway, their demise is not so unfortunate.

Web Forms and Web Controls

There is a new buzzword, "WebForms", which means that a server-side application consisting of (WebForm) classes generates pages that the user might regard as a form. The bottom line is that the developer can employ the "code-behind-forms" click-point-and-forget model to create web pages, drawing "web controls" in a designer that end up as an ASP.NET page at design time, accompanied by a class. Everything is transformed into the final HTML page at run time (generated at the server side, of course). It's like CGI scripts really, only that there aren't mere scripts, but real OO apps. With Web Forms, it's easy to forget that each of the user's actions (except perhaps moving the mouse) results in a gratuitious round trip to the server.

In addition to clicking a Web Form together, you can write Web Controls, which you or maybe someone else can then draw on a web form. In fact, this is an interesting, object-oriented model for fat-server evangelists, and it's definitely easier than writing a Perl script.

So, it seems that most of the application logic, and worse, even the presentation code for web applications is to remain on the server, which means that client ressources are not used while the net becomes more crowded. I'm taking Microsoft's new "nice guy" attitude towards legacy browsers as a hint that things will continue to be server-centric. Perhaps I was hoping that, with XML becoming mainstream, we might see internet clients assume a more active role in the internet, maturing from mere page layouters into real data access applications. However, .NET has more in the box, and one can definitely write rich clients with it.

Web Services

It seems that this is what .NET is all about. Of course, it's a general-purpose application platform, but Web Services have gotten a great deal of attention. In, short, it's way of providing higher-level services over the internet, using an HTTP infrastructure for transportation, and XML as (not only) the data format.

Web Services are cool. Although not all that much can be done with them that couldn't (with greater effort, of course) be done without them, the big pro are the standards (like SOAP [a transport protocol] and UDDI [some sort of yellow pages for the web]) that have started to evolve arround them. Web Services are self-describing, and they ease interaction between disparate systems.

Projects, Builds, IDE


To run even the smallest piece of test code, you need to create a new solution (was project group in old VB) and a new project file. The new Solution Explorer is more flexible (you can now close the entire solution without reopening another). Files you add to your project aren't necessarily compiled, they can also be listed as dependencies or embedded resources.

The main project types are these:

All other projects are variations of these. You can switch easily between, say, a class library and a console app, easing testing. Control projects have no special project type; just use a class library.

Native code

Your source code is compiled into an intermediate format called IL (intermediate language). When you install your app, it can be translated to machine code. This can be better than Java's hotspotting. The speed of .NET Windows apps is comparable to those of classic VB apps, while non-GUI code seems to execute faster. Rumours have it that Microsoft aimed for .NET to be only 10 to 15 percent slower than native code compiled from C++, but I haven't run any tests.

New Visual Studio IDE

Visual Studio comes with a new IDE that has many cool new features like auto-hide tool windows, collapsible code, a to-do-list with shortcuts to code sections, integrated help, and easier window management. The IDE is used for C++ and C# as well, so we get new debugging features like conditional breakpoints, thread watchers, and so on.


The downside is that old Add-ins don't work. And there's also a rumour that Add-ins cannot alter the source code (but that might change for the final release). VS supports macros (like in Office), which you can write in VB.NET. Thy should be easier to use than setting up Add-in projects, creating GUIDs manually and registering the Add-in classes.


If you run a project from the IDE, a binary with special debug symbol will be created; you can't run without creating some output first. Edit-and-continue will not work in Version 1 of VS.NET. If you hit an exception, you cannot continue from that. All in all, debugging isn't as powerful as it was.

As VB.NET integerates with the .NET framework, you can seamlessly step from VB code to C++ code and back.

An interesting feature in classic VB was the Immediate window. In design mode, you could call functions or test expressions. This has changed: in design mode, you can only execute IDE commands. In break mode, however, you can use the "immed" command to switch from command mode to immediate mode, allowing the execution of language statements, like variable assignments.


The VB.NET code editor has instant syntax checking (as before), as well as much-improved background compilation features: if you write a statement that syntactically OK, but would cause a compiler error (such as a missing type-cast), the statement is marked with a line (works like a spellchecker), and you get a tooltip describing the error when you move the mouse over it. A task list entry is created as well (when you correct the faulty statement, it gets removed). Another nice feature is autocompletion of "End" constructs: VB will not just complete procedure skeletons, but other blocks as well. IMO, the rich editing capabilities make up for the loss of some debugging features.