Philippe Lacoude Homepage

VB.Net vs. C#.Net

Philippe Lacoude
April 2005  (Version 1.2)


Summary

This paper enumerates the differences between C# .Net 2003 (C#) and Visual Basic .Net 2003 (VB.Net) and the potential ramifications of these differences with regard to cross-language platform support.  Rather than being a guideline in the selection of either language, it aims at increasing the awareness of a few compatibility issues.

Audience

Because most of the differences between the two languages are subtle at best and arcane at worst, this paper is aimed at experienced programmers with some knowledge of either VB.Net or C#.  Some acquaintance with the CLR is necessary, and the ability to read a listing of intermediate language (IL) can be useful.


 Code Download

  CaseCS     CaseLibrary  
  CaseVB     DocumentationCS  
  EventsCS     EventsVB  
  ExceptionVB     ExplicitCS  
  LateBindingCS     LegacyCS  
  LegacyVB     LegacyVB6  
  MyClassCS     MyClassVB  
  OperatorCS     OptionalVB  
  OptionStrictOffVB     ThreadCS  
  ThreadVB     UnsafeCS  
  UnsignedCS     UnsignedIL  
  UnsignedVB     usingCS  
  WithVB    

Contents

Introduction
Its all about the CLR
Oh, and its also all about the CLR
Semantic Differences?
Legacy VB in C#
With
Event Handling
User-Filtered Exceptions
Where is AddressOf in C#?
Unsigned Types
Background Compilation
Automated Documentation
IsDBNull
Some Real, yet Subtle, Differences
Case sensitivity
Explicit Interface Implementation
Where is MyClass?
Volatile
Operator Overloading
Unsafe code
Finalize This!
Option Strict Off
Optional Parameters
So what shall I do?
Errata


 Download This Article in
 PDF Format

 VB.Net vs. C#.Net 

 Errata

 Check the errata if you have downloaded an earlier version. 

Introduction

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.

Its all about the CLR

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.

Oh, and its also all about the CLR

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.”[1]


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[2].

Semantic Differences?

Legacy VB in C#

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[3] 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[4].  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[5]:


      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[6].

With

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[7].
  • Second, the IL code resulting from the compilation of the two above statements turns out to be identical[8].

Event Handling

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[9].  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[10].  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.”

User-Filtered Exceptions

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:

  1. The blocks Catch code
  2. 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[11], 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_0012dup

  IL_0013brtrue.s   IL_0018

  IL_0015pop

  IL_0016br.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_0020callvirt   instance bool ExceptionVB.CalcException::IsRecoverable()

  IL_0025brtrue.s   IL_002a

  IL_0027ldc.i4.0

  IL_0028:  br.s       IL_002b

  IL_002aldc.i4.1

  IL_002bendfilter

IL_002dpop

  IL_002eldloc.1

  IL_002fcallvirt   instance string [mscorlib]System.Exception::get_Message()

  IL_0034call       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_0048leave.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_006bleave.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


Where is AddressOf in C#?

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[12]:


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[13].  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)


Unsigned Types

Let us consider the following program[14]:


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[15]:


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[16] 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.

Background Compilation

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.

Automated Documentation

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.

IsDBNull

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.

Some Real, yet Subtle, Differences

Case sensitivity

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[17], 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[18]:


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[19]:


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[20] 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.

Explicit Interface Implementation

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[21]:


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