|
Will it be Visual Basic or Visual C#?
Which of these two languages should I select
for my next .Net project? No doubt more
ink has been spilled in this debate than in the Palamite controversy on the
divine essence of Angels.
Instead of looking for differences between the two
languages, I will start by showing their intrinsic similarities.
First, we will have a look at the code generated by each of the two compilers,
and we will then turn to the interactions between the two languages and the
.Net Framework.
There may be room for debate about what language is the
proper tool for a given project. There
is also some room for discussion about what language is syntactically the most
elegant. However, such a discussion is a
small detail when considering the .Net architecture itself: in truth, .Net is a
platform that supports a potentially infinite number of languages and
potentially as many processor instruction sets.
Whether you write a “Hello World” in VB.Net, in J#, in C#,
in managed C++ or in any other .Net-based language, it will end up in the
Intermediate Language (IL) format. The
IL is sometimes referred, quite accurately, as the Common Intermediate Language
(CIL). The “Hello World” IL program code
looks like assembly code, although it does not target a specific processor
instruction set:
.method public hidebysig static void Main () cil managed
{
.entrypoint
Idstr "Hello, World!"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
The assembly code shown above is what .Net is all
about: A language independent and
platform independent, object-oriented,
assembly language. The central truth
about .Net is that a VB.Net programmer is essentially at par with a C#
programmer, and performs the same duty: generate .Net IL code.
This IL code cannot be executed on the computer on which it
is generated, nor on any other computer for that matter, without the help of
the Common Language Runtime (CLR.) The
CLR is in charge of converting the IL code to native (x86) code.
Hence, writing in .Net is essentially writing in something
that will become IL and will be executed by the CLR. By its architecture, .Net was meant to be
independent from a specific language and all .Net code is interoperable
regardless of the originating language.
A programmer could well come up with a new IL-compiler for a new
language of his own design. Essentially,
what the programmer would have to do is map his language's grammar to that of the
CIL. By the magic of the CLR, the
resulting code would instantly be “compatible” with other languages, which is
to mean that the generated objects would be usable in other .Net languages.
Of course, a language may or may not implement every CIL
keyword and feature. It is perfectly
possible to imagine that a language implements only a few IL keywords and still
generates IL-compliant code.
Arguably, Microsoft did a very good job at implementing IL
for the four most widely used .Net languages, i.e. Managed Visual C++, Visual
Basic .Net, C# and J#. With the upcoming
"Whidbey" release of .Net, the gap between the languages and IL
capabilities will be even more tenuous than it currently is with the .Net Framework
1.1.
With rare exceptions, VB.Net and C# implement all the
features of the CIL and of the CLR. This
makes the two languages extremely homomorphic.
Concentrating on which of VB.Net or C# best implements the
.Net framework may lead us to miss a very important point: what really matters is
the proficiency in the .Net Framework libraries.
What makes a “good” .Net programmer?
With over 7800 types, more than 2800 public
classes, and 136 namespaces, proficiency in .Net arguably comes from the
knowledge of its architecture and the experience of its most widely used
classes. In the .Net Framework 1.1, the System
namespace alone has 21 types, 490 public interfaces (out of a total of 842), 56
enumerations, and 641 classes (376 of which are public.)
Dixit Jon Skeet, a C# MVP: “Learning the .NET framework
itself is a much bigger issue than learning either of the languages, and it's
perfectly possible to become fluent in both so don't worry too much
about which to plump for.”
That said, despite the overwhelming similarities between the
two languages, a few differences exist.
These can be categorized in essentially two groups:
- First,
we will turn our attention to the semantic
differences (i.e. the differences in the way the CIL or the CLR
features have been implemented in each language.) These differences do not really impact
programmers. They know how to
achieve a goal in their preferred language. These differences only come into play
when translating code from VB.Net to C# and vice versa. The translation is perfectly
possible but it is not immediately obvious to someone who would
lack experience in one of the two languages.
- The
second type of differences is more fundamental. They concern missing features in one
language or the other. When code is
encountered that uses one of these features, it will not be translated
easily into the other language. I
would argue that these differences are not very important because they
either concern underused programming techniques, or result from deviations
from the letter and spirit of the CLR (e.g. late binding in VB.Net, unsafe
code in C#.) We will examine these
differences later, in the fourth part of this paper.
Visual Basic .NET (VB.Net) introduces several changes to the
Visual Basic language. These changes
were introduced in order to make Visual Basic compliant with the Common
Language Specification (CLS.)
This led to the removal of some Visual Basic functions from
the language specification of VB.Net. In
order to allow a Visual Basic 6.0 application to be upgraded to Visual Basic
.NET, Microsoft added a few functions to the .Net Framework. These functions are located in the
Microsoft.VisualBasic.Compatibility assembly, also known as the Visual Basic
6.0 Compatibility library.
The functions of this library allow the upgrade wizard to
replace Visual Basic 6.0 code with sensibly equivalent lines of VB.Net
code. For example, Visual Basic 6.0 code
would be upgraded from:
Option Explicit
Option Base 1
Private Sub cmdTest_Click()
Dim aWeek As Variant
aWeek =
Array("Mon", "Tue", "Wed", "Thu",
"Fri", "Sat", "Sun")
'
Return Tuesday assuming the lower bound is set to 1
MsgBox (aWeek(2))
End Sub
to:
Option Strict Off
Option Explicit On
. . .
Private Sub cmdTest_Click(ByVal eventSender As
System.Object, _
ByVal
eventArgs As System.EventArgs) Handles cmdTest.Click
Dim aWeek As Object
'UPGRADE_WARNING: Array has a new behavior.
'UPGRADE_WARNING: Couldn't resolve default property of object aWeek.
aWeek = New Object()
{"Mon", "Tue", "Wed", "Thu",
"Fri", "Sat", "Sun"}
' Returns Wednesday because the lower bound is set to 0
MsgBox(aWeek(2))
End Sub
in Visual Basic .Net. The MsgBox
function would come from the Visual Basic 6.0 Compatibility library and the
code would not behave as in Visual Basic 6.0:
The dialog box would output “Wed” instead of “Tue” because all .Net
arrays are zero-based.
New code was not meant to use the functions in
question. Some time in the future,
Microsoft might deprecate this library.
That said, other .Net languages, including C#, could use the various
legacy Visual Basic 6.0 functions by referencing the Microsoft.VisualBasic.dll
assembly. In C#, the above code would be
written as:
string[] aWeek = {"Mon", "Tue",
"Wed", "Thu", "Fri", "Sat",
"Sun"};
// Returns Wednesday because the lower bound is set to 0
Microsoft.VisualBasic.Interaction.MsgBox(aWeek[2],
Microsoft.VisualBasic.MsgBoxStyle.OKOnly, "LegacyVB6");
Needless to say, this is not the proper way to display a
message box and a proficient .Net programmer would instead call the new Show
method of the MessageBox class.
In Visual Basic, every method call is preceded by a
reference to an instance of the object upon which this method operates:
oMyObject.SomeMethod()
When calling several methods of an instance, it is possible
to shorten a code listing and avoid repetitive typing using the With
. . . End With statements.
The following code:
oMyObject.SomeMethod1()
oMyObject.SomeMethod2()
oMyObject.SomeMethod3()
oMyObject.SomeMethod4()
oMyObject.SomeMethod5()
becomes:
With oMyObject
.SomeMethod1()
.SomeMethod2()
.SomeMethod3()
.SomeMethod4()
.SomeMethod5()
End With
There is no equivalent to the With . . . End
With statement in C#. This
is not a very significant issue for two reasons:
- First,
it is generally admitted that the second listing lacks in
readability. A lot of experienced
programmers prefer the first syntax.
- Second,
the IL code resulting from the compilation of the two above statements
turns out to be identical.
Another semantic difference is the event handling techniques
in the two languages. In Visual Basic
.Net it is possible to declare that a method can handle an event whereas, in
C#, each event handler has to be added as a System.EventHandler
to the actual event collection of the class instance.
For example, we could imagine an interface with four buttons
triggering a single task whose behavior depends on which of the four buttons
was clicked. In our example, we will
just display the last-pressed button's name in a text label field.

In VB.Net, the Handles
keyword allows programmers to hook as many events as they want to a method. To display the name of a button, one can
simply write:
Public Class
frmEvents
Inherits
System.Windows.Forms.Form
Windows
Form Designer generated code
Private
Sub ButtonsClick(ByVal
sender As System.Object, ByVal e As
System.EventArgs) _
Handles Button1.Click, Button2.Click, Button3.Click, Button4.Click
lblLastCLicked.Text =
sender.name
End
Sub
End Class
In C#, programmers must create the event handlers explicitly. The code is much longer. Also note that the method does not handle any
events per se: The instances of the
form's button each decide which system event handler to choose from. Arguably, this simplifies the code and makes
it more readable.
Windows
Form Designer generated code
[STAThread] static void Main()
{
frmEvents myForm = new frmEvents();
myForm.button1.Click
+=
new System.EventHandler(myForm.ButtonsClick);
myForm.button2.Click
+=
new System.EventHandler(myForm.ButtonsClick);
myForm.button3.Click
+=
new System.EventHandler(myForm.ButtonsClick);
myForm.button4.Click
+=
new System.EventHandler(myForm.ButtonsClick);
Application.Run(myForm);
}
private
void ButtonsClick(object
sender, System.EventArgs e)
{
lblLastCalled.Text =
sender.ToString().Substring(35);
}
One last word before we change topics. For real life applications, in the C# version,
I would strongly suggest moving this code from the main ( ) { }
function to the constructor located in the hidden IDE-generated section called
“Windows Form Designer generated code.”
The CLR runtime creates an Exception
object each time an exception occurs.
Under its current model, the CLR has an associated table of Exception
Handling (EH) information for each method of an executable. When the exception occurs, the CLR traverses
that table (if it is not empty) and looks for the inner-most block containing
the line that failed. In VB.Net, the
line needs to be included between a Try
and a Catch in a Try . . . Catch
. . . Finally . . . End Try block of code.
If that blocks exception handling information has an
exception handler that matches the type and
the condition of the current exception, the CLR executes two sets of
instructions, in the following order:
- The
blocks Catch code
- The Finally clause
In other cases, the CLR walks up the stack, moving to each previous
caller of the current method, until it finds a match for the exception. If no caller ever matches the exception, the
debugger is called by the CLR. Upon
failure from the debugger to attach to the process in which the exception
occurred (as would be the case in a release version of a program), the runtime
throws the UnhandledException event. If this exception is not caught, the stack is
dumped and the process is terminated.
The CLR implements four types of exception handlers (Catch
statements):
- Finally handlers These execute whatever
happens in the Try or Catch sections.
- Fault handlers They execute if and only if
an exception occurs.
- Type-filtered
handlers They execute only if the type of an exception matches the type
a specified class or the type of a class that Inherits from it.
- User-filtered
handlers They run user-specified conditional
code to find out whether the exception should be handled by the associated
handler or should be passed up to the callers exception handling code.
These four types of exception handlers may or may not be supported by a .Net
language. It turns out that Visual Basic
.NET implements user-filtered handlers (using the Catch . . . When
clause) whereas C# does not implement them.
These so-called user-filtered handlers are in fact conditional exception handlers.
To illustrate VB.Net capabilities in the domain of
Structured Exception Handling (SEH), let us first define an exception
class. Normally, User-Defined Exception
classes are not directly derived from the exceptions base class (i.e. System.Exception.) They rather inherit from the ApplicationException
base class. The following class is an
example of such a User-Defined Exception class:
Public Class CalcException
Inherits
ApplicationException
Public
Sub New()
End
Sub
Public
Sub New(ByVal
Message As String)
MyBase.New(Message)
End
Sub
Public
Sub New(ByVal
Message As String,
ByVal Inner As
Exception)
MyBase.New(Message, Inner)
End
Sub
Public
Function IsRecoverable() As Boolean
If Rnd() > 0.5 Then
IsRecoverable = True
End
Function
End Class
This type of exception can be coded indifferently in VB.Net
or in C#.Net. However, only VB.Net can
make use of the Boolean function inside of the Catch
clause:
Option Explicit On
Option Strict On
' Structured Exception Handling (SEH)
Sample
' User-Filtered Exceptions
Module Filtering
Sub
Main()
Dim I As Byte
For I = 1 To 5
Try
' Mess things up (Big Bad Bug)
Throw New
CalcException("A Big Bad Bug crashed the code.")
Catch ExCalc As CalcException
When ExCalc.IsRecoverable
Console.WriteLine(ExCalc.Message)
Console.WriteLine("Mini boom! (Recovered
fine.)" & vbCrLf)
Catch Ex As Exception
' Well, turns out it was not recoverable after all
Console.WriteLine(Ex.Message)
Console.WriteLine("Kaboom! (Not recovered.)" & vbCrLf)
End Try
Next
Console.ReadLine()
End
Sub
End Module
The function could not be used in such manner in C# (due to
the absence of an equivalent to VB.Net's When
keyword.) Of course, some C# code
contained in the first Catch clause could
conditionally handle the exception based on the state of this CalcException
instance. Should the condition not be
met, the catch clause block could throw the exception and let the CLR pass it
up to the next SHE block.
The reason why a C# equivalent to the above Main()
function would be more tortuous is that C# does not offer an implementation the
IL filter instruction. As we can see in the color-coded listing of
the disassembled code of the above .Net program,
VB.Nets Catch . . . When clause
Catch
ExCalc As CalcException When ExCalc.IsRecoverable
was replaced by the following IL block:
.try IL_0002 to IL_000d filter IL_000d handler IL_002d to
IL_004a
The IL code is interesting to read because it illustrates
the mechanisms at work in the CLR during a SEH execution:
- The .try instructions (on lines IL_004a and IL_006d)
represent the two entry points of the above mentioned EH table.
These instructions indicate that they
handle the guarded section of the code (which I color-coded in green and marked by the ♠ sign.) When
an exception occurs, the runtime compares these two EH table entries
addresses, i.e. compares the location at which the exception occured to
the TryOffset (IL_0002
in our case) and TryLength (12, or 0x0c in our
case) of each two EH clauses.
- The
first .try instruction notifies the CLR of the
conditional nature of the EH via the filter
keyword. In our case, since this
entry comes first in our EH table, and since it matches the exception locus,
the CLR has to determine if the EH code can be engaged. Obviously, this is so if the filter condition is met.
- This
filter is followed by the line number of
the beginning of the filter block
(which is color-coded in bold blue characters and start at the ♣ sign.)
The filter block calls
the IsRecoverable method of our User-Defined
Exception. As per the IL
specification, the decision is passed back to the execution engine in the
form of an Int32 value, always 0 or 1, placed at the top of
the stack. As per line IL_0025, the code “branches if true” (brtrue.s)
to the line IL_002a where the
engine “loads a 4-byte integer of value 1 on the stack” (ldc.i4.1) and ends the filter (endfilter.) If the
IsRecoverable
function returns false the execution
proceeds from IL_0025 to IL_0027 without branching and the engine
“loads a 4-byte integer of value 0 on the stack” (ldc.i4.0) and ends the filter (endfilter.)
Any value other that 0 or 1 on the stack
when endfilter is reached would lead
to an execution engine failure.
- The orange color-coded section beginning at the ♥ sign is trivial and corresponds to
the first handlers section.
Similarly, the red color-coded
section beginning at the † sign
handles the second EH clause and is called whenever the IsRecoverable function returns false.
- The
SHE block terminates by clearing the exception with a ClearProjectError() clause. This clause is the antonym of the Throw instruction, which is translated by
SetProjectError() in IL (Cf. line IL_001a.)
.method public static void
Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 125 (0x7d)
.maxstack 2
.locals init (unsigned int8 V_0, class ExceptionVB.CalcException V_1,
class [mscorlib]System.Exception V_2)
IL_0000: ldc.i4.1
IL_0001: stloc.0
♠ IL_0002: ldstr "A Big Bad Bug crashed the code."
IL_0007: newobj instance void ExceptionVB.CalcException::.ctor(string)
IL_000c: throw
IL_000d: isinst ExceptionVB.CalcException
♣ IL_0012: dup
IL_0013: brtrue.s IL_0018
IL_0015: pop
IL_0016: br.s IL_0027
IL_0018: dup
IL_0019: stloc.1
IL_001a: call void [Microsoft.VisualBasic]
Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError
(class [mscorlib] System.Exception)
IL_001f: ldloc.1
IL_0020: callvirt instance bool ExceptionVB.CalcException::IsRecoverable()
IL_0025: brtrue.s IL_002a
IL_0027: ldc.i4.0
IL_0028: br.s IL_002b
IL_002a: ldc.i4.1
IL_002b: endfilter
♥ IL_002d: pop
IL_002e: ldloc.1
IL_002f: callvirt instance string [mscorlib]System.Exception::get_Message()
IL_0034: call void[mscorlib]System.Console::WriteLine(string)
IL_0039: ldstr "Mini boom! (Recovered fine.)\r\n"
IL_003e: call void [mscorlib]System.Console::WriteLine(string)
IL_0043: call void [Microsoft.VisualBasic]
Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_0048: leave.s IL_006d
† IL_004a: dup .try
IL_0002 to IL_000d
filter IL_000d handler
IL_002d to IL_004a
IL_004b: call void [Microsoft.VisualBasic]
Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError
(class [mscorlib] System.Exception)
IL_0050: stloc.2
IL_0051: ldloc.2
IL_0052: callvirt instance string [mscorlib]System.Exception::get_Message()
IL_0057: call void [mscorlib]System.Console::WriteLine(string)
IL_005c: ldstr "Kaboom! (Not recovered.)\r\n"
IL_0061: call void [mscorlib]System.Console::WriteLine(string)
IL_0066: call void [Microsoft.VisualBasic]
Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_006b: leave.s IL_006d
IL_006d: ldloc.0 .try
IL_0002 to IL_000d
catch [mscorlib]System.Exception handler
IL_004a to IL_006d
IL_006e: ldc.i4.1
IL_006f: add
IL_0070: conv.ovf.u1
IL_0071: stloc.0
IL_0072: ldloc.0
IL_0073: ldc.i4.5
IL_0074: ble.s IL_0002
IL_0076: call string [mscorlib]System.Console::ReadLine()
IL_007b: pop
IL_007c: ret
} // end of method Filtering::Main
The creation of delegates for threading is another semantic
difference between VB.Net and C#. In
Visual Basic .Net, the old Visual Basic 6.0 AddressOf
function was resuscitated to reference the entry point of a method:
Dim T As New
Thread(AddressOf ZProgress)
In the previous line, we define a thread which will execute
the ZProgress method upon starting.
Let us look at an example of a multithreaded VB.Net program
that executes two subroutines (e.g. a series of calculus) in parallel. The GUI lets the user start each of the two
tasks on demand and can visualize the progress of each one:

The program that achieves the above requirement is listed below:
Imports System.Threading
Imports System.Windows.Forms
Public Class
frmThreadDemo
Inherits
System.Windows.Forms.Form
Windows Form Designer
generated code
Private
Sub btnStart_Click(ByVal
sender As System.Object, ByVal e As
System.EventArgs) _
Handles
btnStart1.Click, btnStart2.Click
Dim oDS As DoSomething
' Which event (button click) triggered the method?
If sender.Name = "btnStart1" Then
oDS = New DoSomething(pbThread1)
Else
oDS = New DoSomething(pbThread2)
End If
sender.Enabled = False
' (Re)start the thread
oDS.Start()
End
Sub
End Class
Friend Class
DoSomething
Private
myPB As ProgressBar
Sub
New(ByRef PB As ProgressBar)
'Set the progress bar to zero
myPB = PB
myPB.Value = 0
End
Sub
Sub
Start()
Dim T As New
Thread(AddressOf ZProgress)
T.Start()
End
Sub
Private
Sub ZProgress()
' Big calculus
Dim I As Integer
For I = 1 To 100
Thread.Sleep(500)
myPB.Value = I
Next
End
Sub
End Class
The class in charge of the calculations is the DoSomething
object (listed above.) The actual task
takes place in the ZProgress()
function. However, because we do not
want to hang the GUI until completion of the task, the event handler for the
buttons click does not call the progress: it calls Start()
instead. The Start()
method creates a new thread, dedicates it to the execution of the ZProgress()
method (which is part of the same class), starts the thread, and returns the
execution of the main thread to the applications GUI. The new thread lives on its own and executes the
big calculus routine (which is mostly about sleeping in our example.)
In C#, the closest version of the DoSomething
object (listed below) would have a sensibly different Start()
method. In C#, in the absence of an AddressOf
keyword, the thread delegate is defined outside of the thread's object
definition. The threads constructor
takes a single argument, which is the ThreadStart
delegate. The thread is now ready to
start, which we do by calling the threads Start()
method (in the same manner as in VB.Net.)
class
DoSomething {
. . . . .
public void Start()
{
ThreadStart T = new ThreadStart(ZProgress);
Thread t = new Thread(T);
t.Start();
}
public void ZProgress()
{
// Big calculus
for(int I=0; I <
100; I++)
{
Thread.Sleep(500); // Half second
myPB.Value = I;
}
}
}
In C#, to create delegate instances, the instantiation
process must specify the delegate type, the method, and the target (when the
delegate targets a different instance or type from the calling thread.) The target class would be specified as:
new ThreadStart(anotherType.aStaticMethod)
or:
new ThreadStart(aMember.anInstanceMethod)
Let us consider the following program:
using System;
namespace UnsignedCS
{
class
TestSign
{
static private ushort i16bits = 100;
static private uint i32bits = 200;
static private ulong i64bits = i16bits - i32bits;
[STAThread] static void Main(string[] args)
{
Console.WriteLine("i64bits = {0}", Test1());
Console.WriteLine("i16bits - i32bits = {0}", Test2());
Console.WriteLine("(int) i16bits - (int) i32bits = {0}",
Test3());
Console.ReadLine();
}
static public ulong Test1() { return
i64bits; }
static public ulong Test2() { return
i16bits-i32bits; }
static public int Test3() { return
(int) i16bits - (int)
i32bits;}
}
}
The program uses three unsigned types ushort,
uint and ulong
which cannot contain negative values.
The calculations lead to implicit type conversions in two of the
programs lines:
ulong i64bits = i16bits - i32bits;
. . .
Console.WriteLine("i16bits - i32bits = {0}", i16bits -
i32bits);
The output of the above program is:
i64bits = 4294967196
i16bits - i32bits = 4294967196
(int) i16bits - (int) i32bits = -100
A noticeable issue is the value of the difference. It should be equal to -100 but cannot be
because the variable type does not support negative values. Because -100 is equal to 0xFFFFFFFFFFFFFF9C
in signed hexadecimal, and because 0xFFFFFF9C is equal to 4294967196 in
decimal, the program outputs 4294967196 as the difference. This is clearly wrong and I will comment on
this issue later.
The C# language reference defines the three ushort,
uint and ulong
unsigned types but VB.Net does not define any of these three types.
VB.NET limits its data types to those in the Common Language
Specification (CLS.) The CLS includes a
section on the Common Type System (CTS.)
The CTS provides the standard set of data types. The CTS is what makes all .Net-based languages
interoperable. The CTS defines the data
types that can be used across all .NET languages. Thanks to the CTS, .NET offers a unified
programming model and allows programmers to mix multiple languages within a
single application.
By creating C# functions that return unsigned integers, you
go outside of the boundaries of the CTS.
When a C# class, whose members use unsigned types, is instantiated in VB.Net,
the passing variables end up using a System type. This is because, in C#, each of the three
above keywords is mapped to a System type, respectively System.UInt16,
System.UInt32 and System.UInt64. The architects of C# had to resort to this
stratagem because the CLS & CTS do not offer such types natively.
When variables are received from C# libraries as unsigned
types, they are defined as a system unsigned integer such as System.UInt16 in
VB.Net. It forces the VB.Net code to
resort to system conversion (such as Convert.ToInt16 or Convert.ToInt32) to use
these types values.
For example, the following VB.Net code makes use of the C# object
defined above:
Imports UnsignedLibCS
Module SignTest
Sub
Main()
Dim objectCS As TestSign
Dim i64bits As System.UInt64 =
objectCS.Test1()
Console.WriteLine("i64bits = {0}", i64bits)
Console.WriteLine("i16bits - i32bits = {0}", objectCS.Test2())
Console.WriteLine("(int) i16bits - (int) i32bits = {0}",
objectCS.Test3())
Console.ReadLine()
End
Sub
End Module
This illustrates how VB.Net deals with non-CTS compliant C#
types. The code outputs the same (erroneous)
results as the C# version. However, VB.Net
could not compile the following code because:
- The casting of 100 to System.UInt16 is not
allowed.
- The casting of 200 to System.UInt32 is not
allowed.
- The
minus operator is not defined between types System.UInt16 and
System.UInt32.
Sub Impossible()
Dim
i16bits As System.UInt16 = 100
Dim
i32bits As System.UInt32 = 200
Console.WriteLine("i16bits
- i32bits = {0}", i16bits - i32bits)
End Sub
Using the C# unsigned types in a library thus leads to a
first problem: it will be more complicated to consume that library in another
.Net platform language. A basic rule of
“good” C# programming could be to try to stick to CTS-compliant types.
The second problem due to using C# unsigned types in a
library is the potential catastrophic effect of the type conversions. As we saw above, the subtraction of 200 from
100 does not generate an overflow error but rather leads to an erroneous result. This is due to a “bug” in the IL code
generated by the C# compiler:
.method public hidebysig static unsigned int64
Test2() cil managed
{
// Code size 17 (0x11)
.maxstack 2
.locals init ([0]
unsigned int64 CS$00000003$00000000)
IL_0000: ldsfld
unsigned int16 UnsignedLibCS.TestSign::i16bits
IL_0005: ldsfld
unsigned int32 UnsignedLibCS.TestSign::i32bits
IL_000a:
sub
IL_000b:
conv.u8
IL_000c:
stloc.0
IL_000d:
br.s IL_000f
IL_000f: ldloc.0
IL_0010: ret
} // end of method TestSign::Test2
This “bug” appears because the C# compiler does not check
for arithmetic overflows by default:

As we can see in the printout of the Test2()
function, the C# compiler loads both fields on the stack (ldsfld),
subtracts the two fields (sub) and converts
the resulting value on top of the evaluation stack to an unsigned
int64 and extends it to int64
(conv.u8).
The problem with this code is that the sub
OpCode does not check for integer overflows.
The proper instruction to prevent the error is sub.ovf.un,
which checks for overflows on unsigned integers. Similarly, the conv.u8
should be replaced by the conv.ovf.u8.un
OpCode, which converts the unsigned value at the top of the stack to an unsigned
int64, throwing an exception on overflow.
Let us add that since there is no need to:
- store
the top of the stack in the first local variable (stloc.0),
- unconditionally
branch to the next line (br.s
IL_000f) and
- place
back the content of the first local variable onto the stack (stloc.0),
the three statements before last could be skipped. This would remove the need for a local field.
The optimized version of our function would read:
.method public
hidebysig static unsigned int64 Test() cil managed
{
.maxstack 2
IL_0000: ldsfld
unsigned int16 UnsignedLibIL.TestSign::i16bits
IL_0005: ldsfld
unsigned int32 UnsignedLibIL.TestSign::i32bits
IL_000a:
sub.ovf.un
IL_000b:
conv.ovf.u8.un
IL_000c: ret
} // end of method
TestSign::Test
Let us consider the new following code (written in VB.Net):
Imports UnsignedLibIL
Module Test_IL
Sub
Main()
Dim TS As TestSign
TS.i16bits =
System.UInt16.Parse("100")
TS.i32bits = System.UInt32.Parse("200")
Try
Console.WriteLine(TS.Test().ToString & vbCrLf)
Catch Ex As Exception
Console.WriteLine(Ex.ToString & vbCrLf)
End Try
TS.i32bits =
System.UInt32.Parse("5")
Console.Write(TS.Test().ToString)
Console.ReadLine()
End
Sub
End Module
The above code
would respectively display an exception and the value 95:
System.OverflowException: Arithmetic operation resulted in an
overflow.
at
UnsignedLibIL.TestSign.Test()
at
UnsignedIL.Test_IL.Main() in .\TestIL.vb:line 9
95
The fact that the usage of unsigned types in C# complicates
cross-language support and is weakly implemented by the compiler (absence of default
overflow exception handling) is a good incentive to stay away from these
features.
In VB.Net, the IDE uses background compilation to provide
the Intellisense, informs the WinForms and ASP page designers, and populates
the top-level dropdowns and the object browser.
The background compilation also enables the property dialog boxes and
the Go to definition contextual menus.

The background compilation is brilliant. Even before we are done typing, the IDE has
pulled a couple potential endings for a statement or detected an error:

The downside of background compilation is that it consumes a
fair amount of resources. A developer
workstation needs to be up-to-date to handle it. On large projects, the feature turns out to
be very slow, especially on Visual Studio .Net 2002. Apparently, Microsoft has done some
optimization and Visual Studio .Net 2003 does not suffer as much of performance
hits when editing large VB.Net projects.
That said, the IDE under VB.Net is still not at par with the
C# IDEs speed when it comes to getting an objects members, events and
properties through Intellisense.
With the C# language, Microsoft introduced XML tags that can
be added to the comments of a program as a markup for documentation. At compile time, programmers can generate a
(nice looking) HTML-based documentation of the code.
An example of such documentation process can be found in the
following listing:
using System;
namespace DocumentationCS
{
/// <summary>
/// The PI class implements a method that return the value of
Pi
/// with various degrees of precision.
/// </summary>
public
class Pi
{
/// <summary>The Digits method returns Pi with 2 or 3 decimals.
/// <seealso
cref="CircleSurface"/>
/// </summary>
/// <param
name="NbDigits">Precision: number
of digits (2 or 3)</param>
/// <returns>Pi</returns>
static public double Digits(int
NbDigits)
{
if (NbDigits == 2)
{
return 3.14;
}
else
{
return 3.141;
}
}
/// <summary>The CircleSurface method returns the surface
/// of a circle. It relies on the Pi() method.
/// <seealso
cref="Digits"/>
/// </summary>
/// <param
name="Diameter">Diameter of the
circle</param>
/// <param
name="NbDigits">Precision: number
of digits (2 or 3)</param>
/// <returns>A surface</returns>
public double CircleSurface(double Diameter, int
NbDigits)
{
return Diameter*Diameter*Pi.Digits(NbDigits);
}
}
}
The documentation comments can precede classes, delegates,
interfaces, fields, events, properties and methods. When present in source code files, they are
processed and added to the documentation.
An additional overlooked feature of the inline documentation
is the IntelliSense support: programmers
can invoke /doc:file.xml as a compilation directive and generate an XML based
manifest for their classes and objects:

The only thing to do to implement this feature is to save
the generated .xml file in the same directory as the assembly prior to
referencing it in a project. If the name
of the XML file matches that of the assembly, Visual Studio .Net is able to load
the XML and provide additional information about properties, events and code. This
is a cross-language feature and the VB.Net IDE can display the extra
information contained in the documentation file:

This feature is coming to VB.Net with the new “Whidbey”
release (Visual Studio 2005). Some tools
do a similar job with the existing VB.Net code already, provided that the code is properly
commented. It is also always possible
(but tedious) to manually write the XML-based manifest.
VB.Net implements a
special version of the null
pointer for database fields comparison.
The IsDBNull
function returns a Boolean
value indicating whether an expression evaluates to the System.DBNull
class:
Public
Function IsDBNull(ByVal MyObj As Object) As Boolean
The IsDBNull
function returns True
if the data type of MyObj
evaluates to the DBNull
type; otherwise, IsDBNull
returns False. The System.DBNull
value indicates that the Object
represents missing or nonexistent data. DBNull
is not the same as Nothing,
which indicates that a variable has not yet been initialized. It is also different from a zero-length
string (""),
which is sometimes referred to as a null
string.
Although the
function is part of the language in VB.Net, its usage is not compulsory and it
can be replaced by a direct comparison to the more .Net-like System.DBNull.Value.
One can also use the
IsDBNull
method of the System.Convert
class, which is defined by the following interface:
Public
Shared Function IsDBNull( _
ByVal value As Object _
) As Boolean
Since there is no
equivalent of the IsDBNull
function in C#, it is possible to use the above function in C# as well. The function returns true
if value is of type TypeCode.DBNull
and false
otherwise.
A major difference between the two languages is that VB.Net
is not case-sensitive, contrary to C#. This may lead to some issues in VB.Net
when referencing classes that are part of an assembly written in C#, in J#, in
managed C++, or in any case-sensitive language.
Lets consider the following class library,
written in C#. The Foo
class simply outputs the string “bar”
in a case that depends on that of the name of the method being called:
using System;
namespace CaseLibrary
{
public class Foo
{
public string BAR()
{
return "BAR";
}
public string bar()
{
return "bar";
}
}
}
Because C# is case-sensitive, the compiler can distinguish
between the two BAR and bar
methods. The following code compiles and
executes properly, returning the two strings in the expected order and waiting
for a user input to terminate:
using System;
namespace CaseCS
{
class CaseSensitive
{
[STAThread]
static void Main(string[] args)
{
CaseLibrary.Foo oFoo
= new CaseLibrary.Foo();
Console.WriteLine(oFoo.BAR());
Console.WriteLine(oFoo.bar());
Console.ReadLine();
}
}
}
It is possible to reference the CaseLibrary
in VB.Net, as it would be done with any .Net assembly.

However, the VB.Net program that most closely matches the C#
one just above would not even compile properly:
Imports CaseLibrary
Module CaseSensitive
Sub
Main()
Dim oFoo As New Foo()
Console.WriteLine(oFoo.BAR)
Console.WriteLine(oFoo.bar)
Console.ReadLine()
End
Sub
End Module
The IDE returns a cryptic code signaling that it failed
distinguishing between the two methods bar
and BAR.
Overload resolution failed because no accessible 'BAR' is most
specific for these arguments:
'Public Overloads
Function bar() As String': Not most specific.
'Public Overloads
Function BAR() As String': Not most specific.
If the programmer tries to force a compilation of the code,
the compiler returns BC30521 as the error
code.
The only way for a VB.Net program to call either of the bar
or BAR functions is to use reflection on the CaseLibrary
as follows:
Module CaseReflection
Sub
Main()
ReflectionFoo()
End
Sub
Sub
ReflectionFoo()
Dim oFoo As New CaseLibrary.Foo()
Dim oType As Type = oFoo.GetType()
Dim Members As Array = _
oType.GetMethods(System.Reflection.BindingFlags.Public _
+
System.Reflection.BindingFlags.Instance)
Dim oMI As
System.Reflection.MethodInfo, oObject As Object
For Each oMI In Members
If (LCase(oMI.Name) = "bar") Then
oObject =
System.Activator.CreateInstance(oType)
Console.WriteLine(oMI.Invoke(oObject, Nothing))
End If
Next
Console.ReadLine()
End
Sub
End Module
In the above code, the For Each Next
loop traverses the list of the methods until it finds one by the right
name. It then invokes the method on an
object oObject of the same type as Foo:
oMI.Invoke(oObject, Nothing)
Even though it meets the goal of running the bar
methods, the above code is convoluted and would not be considered as an
appropriate solution.
Before concluding on this subject, let us add that this
problem is not limited to the use of interfaces with identical names (case
notwithstanding.) This can also happen
if a C# program uses an upper case version of a VB.Net keyword. Let us consider the following C# code:
using System;
namespace CaseLibrary
{
public
class GET
{
public string SET()
{
return "SET";
}
}
}
The following VB.Net code could not be compiled and would
lead to a warning message in the Visual Studio .Net 2003 IDE:
Imports CaseLibrary
. . .
Dim oKeyword As New GET()
A VB.Net program
can only use this object by explicitly referencing the CaseLibrary
namespace (as in the CaseLibrary.GET()
expression) or by adding square brackets around the objects name:
Imports CaseLibrary
Module CaseGET1
Sub
Main()
Keyword1()
keyword2()
Console.ReadLine()
End
Sub
Sub
Keyword1()
Dim oKeyword As New CaseLibrary.GET()
Console.WriteLine(oKeyword.SET)
End
Sub
Sub
Keyword2()
Dim oKeyword As New [GET]()
Console.WriteLine(oKeyword.SET)
End
Sub
End Module
This syntax should be familiar and is similar to the manner
by which a VB.Net class can be named using one of the languages reserved
keywords:
Class [GET]
End Class
The previous examples show that the case-sensitivity of C#
needs to be manipulated with precaution.
This is especially true of C# class libraries that are going to be used
by VB.Net client applications.
A very useful (yet overlooked) feature of C# is the explicit
interface implementation. It allows writing classes while implementing several identical interfaces.
For example, let us say that a programmer wants to create an
encryption library that encrypts and decrypts data using two different
underlying technologies. Of course, one
way to do so is to pass a parameter each time a routine is called. The parameter specifies which technology to
use.
A better way to achieve the same result is to explicitly implement each member of the
underlying interfaces:
using System;
namespace ExplicitCS
{
interface
IEncryption128bits
{
string Encrypt(string Data, string Key);
string
Decrypt(string Data, string
Key);
}
interface
IEncryption56bits
{
string Encrypt(string Data, string Key);
string Decrypt(string Data, string Key);
}
class
Encryption : IEncryption56bits, IEncryption128bits
{
private string LoneRoutine(string Data, string
Key, int KeySize, string
Dir)
{
return "(" + Data + "," + Key +
")_" + Dir + KeySize + "bits";
}
#region IEncryption56bits Members
string IEncryption56bits.Encrypt(string Data, string
Key)
{ return
LoneRoutine(Data, Key, 56, "E");
}
string IEncryption56bits.Decrypt(string Data, string
Key)
{ return
LoneRoutine(Data, Key, 56, "D");
}
#endregion
#region IEncryption128bits Members
string IEncryption128bits.Encrypt(string Data, string
Key)
{ return
LoneRoutine(Data, Key, 128, "E");
}
string IEncryption128bits.Decrypt(string Data, string
Key)
{ return
LoneRoutine(Data, Key, 128, "D");
}
#endregion
}
}
In the above listing, the code in the #region
IEncryption56bits Members implements the IEncryption56bits
interface. Similarly, the code in the #region
IEncryption128bits Members implements the IEncryption128its
interface. The Encryption
class could (but does not in our example) implement its own variation of the Encrypt
and Decrypt methods.
To access the encryption/decryption routines, it is
necessary to call the correct implementation.
This is done via two techniques:
- It
is possible to create a variable having the type of one of the two
interfaces and set it equal to an instance of the Encryption class. All
the subsequent calls to that interface variable will automatically
be mapped to the proper encryption technology.
- It
is possible to cast the instance of the Encryption
class to one of the two interfaces and directly call a method, i.e.
without the support of an intermediate stub.
The code below exposes each of the two techniques:
using System;
namespace ExplicitCS
{
class
MainThread
{
[STAThread] static void Main(string[] args)
{
// Create an encryption class
Encryption oEnc = new Encryption();
// Access the encryption routines
IEncryption56bits
iEnc56 = (IEncryption56bits) oEnc;
IEncryption128bits
iEnc128 = (IEncryption128bits) oEnc;
// Encrypt and
decrypt using each of the two interfaces
Console.WriteLine(iEnc56.Encrypt("Some text","tiny
key"));
Console.WriteLine(iEnc128.Encrypt("Some text","BIG
KEY"));
Console.Read();
Console.WriteLine(((IEncryption56bits) oEnc).Decrypt("Some
text","tiny key"));
Console.WriteLine(((IEncryption128bits) oEnc).Decrypt("Some
text","BIG KEY"));
Console.Read();
}
}
}
In VB.NEt, it is not possible to name the two implementations by the same name
(cf. errata section). The following VB.Net equivalentto the C# code would simply
not compile:
Function Encrypt(ByVal
Data As String, ByVal Key
As String) As String _
Implements IEncryption56bits.Encrypt
Encrypt = LoneRoutine(Data, Key, 56, "E")
End Function
Function Encrypt(ByVal
Data As String, ByVal Key
As String) As String _
Implements IEncryption128bits.Encrypt
Encrypt = LoneRoutine(Data, Key, 128, "E")
End Function
The correct VB.Net code would |