Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

VB vs. C# MSIL Code Generation: Are the results equal?

0.00/5 (No votes)
7 Feb 2005 13  
A discussion of some differences between VB and C# MSIL code.

Introduction

The intent of this article is to once and for all prove, by means of empirical evidence, that the MSIL code generated by the VB and C# compilers are not equal, particularly when dealing with non void methods and string value comparisons. In order to prove this point, a contrast will be made between the MSIL code produced by each compiler for two identical assemblies, one coded in VB, the other in C#, with possible explanations as to why the differences exist. Afterwards, the significance of this discrepancy will be briefly discussed along with some biased comments intended to flare up the VB vs. C# language wars.

Test Assemblies

The two assemblies used in the test are identical in all respects. However, here I only provide the code for the one and only type each assembly exposes, as well as the resulting MSIL code representing the only two methods the identical types expose. To verify that �other factors� are equal as well, such as optimization settings, feel free to download the code. Finally, both assemblies were coded and compiled using VS.NET 2003 (.NET 1.1 SP1) on a Windows XP SP2 machine. Moreover, the MSIL code was obtained using ildasm.exe (version 1.1.4322.573).

VB type ClassLibrary1.Class1:

Public Class Class1

    Public Sub New()

    End Sub

    Public Function foo1(ByVal test As Boolean) As Integer
        Dim i As Integer
        If (test) Then
            i = 1
        Else
            i = 2
        End If
        Return i
    End Function

    Public Sub foo2(ByVal test As Boolean)
        Dim s As String = Nothing
        If s = String.Empty Then
        End If
    End Sub

End Class

C# type ClassLibrary1.Class1:

public class Class1
{
    public Class1()
    {

    }

    public int foo1(bool test)
    {
        int i;
        if(test)
            i=1;
        else
            i=2;
        return i;
    }

    public void foo2(bool test)
    {
        string s = null;
        if(s == string.Empty){};
    }
}

So now we have two identical types, the former written in VB and the latter written in C#. First, let�s compare the MSIL code each compiler produces for member foo1.

VB MSIL (foo1):

.method public instance int32  foo1(bool test) cil managed
{
  // Code size       11 (0xb)

  .maxstack  1
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  ldarg.1
  IL_0001:  brfalse.s  IL_0007
  IL_0003:  ldc.i4.1
  IL_0004:  stloc.1
  IL_0005:  br.s       IL_0009
  IL_0007:  ldc.i4.2
  IL_0008:  stloc.1
  IL_0009:  ldloc.1
  IL_000a:  ret
} // end of method Class1::foo1

C# MSIL (foo1):

.method public hidebysig instance int32  foo1(bool test) cil managed
{
  // Code size       11 (0xb)

  .maxstack  1
  .locals init (int32 V_0)
  IL_0000:  ldarg.1
  IL_0001:  brfalse.s  IL_0007
  IL_0003:  ldc.i4.1
  IL_0004:  stloc.0
  IL_0005:  br.s       IL_0009
  IL_0007:  ldc.i4.2
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method Class1::foo1

At first glance, the MSIL code produced by both compilers looks identical. First, each version has the same number of MSIL code lines as the other. Second, both versions have identical .maxstack directives, that is, each method expects to use the same number of stack slots as the other. However, if you look at the third line of MSIL code, the .locals init directive, which is used to declare variables and assign initial values to them, a striking difference exists between both versions:

VB: .locals init (int32 V_0,int32 V_1)

C#: .locals init (int32 V_0)

Why is it that, despite the fact that method foo1 makes use of one, and only one, local variable (i), the VB MSIL code declares and initializes an extra variable, variable V_0, one which is never used? On the other hand, the C# MSIL code declares and initializes one, and only one, variable, which makes absolute sense for the obvious reasons already stated.

Now the question becomes, why the discrepancy, regardless of how significant or insignificant it may be. I�m no expert, especially when dealing with MSIL, yet I am almost 100% certain that the unused variable is there only because, unlike C#, VB allows a function�s code path to not return a value, and when this happens, the unused V_0 variable becomes used. In other words, in VB, you can have a function with a certain return type, yet if you do not explicitly return a value of that type, the compiler will return the type�s default value for you, hence, the existence of the mysterious V_0 variable. To prove my point, let�s comment out the last line of the VB version of the foo1 method, the line that explicitly returns the function�s value.

Public Function foo1(ByVal test As Boolean) As Integer
      Dim i As Integer
      If (test) Then
           i = 1
      Else
          i = 2
      End If
       'Return i

End Function

After the above change is made and compiled, the resulting MSIL code looks like:

.method public instance int32 foo1(bool test) cil managed
{
  // Code size       11 (0xb)

  .maxstack  1
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  ldarg.1
  IL_0001:  brfalse.s  IL_0007
  IL_0003:  ldc.i4.1
  IL_0004:  stloc.1
  IL_0005:  br.s       IL_0009
  IL_0007:  ldc.i4.2
  IL_0008:  stloc.1
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method Class1::foo1

The only difference between both versions of the VB foo1 MSIL code is in the second to last line. The first version, which explicitly returns a value, pushes variable V_1 onto the stack, via ldloc.1, since V_1 represents variable i, which in turn holds the value that is explicitly returned. Contrast that to the second version, which doesn�t explicitly return a value and, thereby, results in the compiler pushing variable V_0, which always holds the default value of the function�s return type, onto the stack by means of instruction ldloc.0.

So the moral of the story here is that for any non void method written in VB, the compiler will generate MSIL code that always declares and initializes an extra local variable of the same type as its containing function, a variable which may or may not be used, respectively depending on whether the function does not explicitly return a value. My personal opinion here is that, although the extra variable is insignificant with respect to its impact on performance and memory consumption, nonetheless the VB compiler should be smart enough to include the extra variable if, and only if, the member�s code path does not return a value. If it does, then the declaration and initialization of the extra variable is without question pointless.

Now, let�s move on and take a look at the MSIL code the VB and C# compilers generated for method foo2.

VB MSIL (foo2):

.method public instance void  foo2(bool test) cil managed
{
  // Code size       18 (0x12)

  .maxstack  3
  .locals init (string V_0)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  ldsfld     string [mscorlib]System.String::Empty
  IL_0008:  ldc.i4.0
  IL_0009:  call       int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.
                                 CompilerServices.StringType::StrCmp(string,
                                 string,
                                 bool)
  IL_000e:  ldc.i4.0
  IL_000f:  bne.un.s   IL_0011
  IL_0011:  ret
} // end of method Class1::foo2

C# MSIL (foo2):

.method public hidebysig instance void  foo2(bool test) cil managed
{
  // Code size       15 (0xf)

  .maxstack  2
  .locals init (string V_0)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  ldsfld     string [mscorlib]System.String::Empty
  IL_0008:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_000d:  pop
  IL_000e:  ret
} // end of method Class1::foo2

Well, it�s obvious with just a quick glance of the two versions of MSIL code generated for method foo2 that differences do exist, moreover, much more obvious differences than was the case with foo1.

First, comparing the code size comments for each version, it�s clear that more MSIL code is involved with the VB version (//Code size 18 (0x12)) than with the C# version (//Code size 15 (0xf)).

Second, the VB version expects and will use three stack slots (.maxstack 3), while the C# version expects and will use only two stack slots (.maxstack 2).

From there on, all remains equal until we reach line IL_0008, at which point both versions drastically diverge.

The C# version first pushes onto the stack the Boolean result of the call made to [mscorlib]System.String::op_Equality, which is the function C# uses to compare string values when using the == operator. Then, it immediately removes (pop) this same Boolean value from the stack right before returning execution to the caller. Although the last pop seems pointless, there�s really no choice in the matter because a method�s evaluation stack must be left empty before returning control to the caller, unless a value is to be returned, as is the case with non void methods.

On the other hand, the VB version first pushes onto the stack the value 0 (ldc.i4.0), which will subsequently be popped off the stack as an argument to the call made to Microsoft.VisualBasic.CompilerServices.StringType::StrCmp, which is the function VB uses to compare string values when using the = operator, the Integer result of which is pushed onto the stack. Once that�s done, instruction ldc.i4.0 is once again used to push the value 0 onto the stack so that it may be subsequently compared to the Integer result of StrComp. Then, by means of instruction bne.un.s IL_0011, the last two values that were pushed onto the stack, that is, the results of the calls made to StrCmp and ldc.i4.0, are popped off, compared, and if inequality results, execution transfers to instruction IL_0011, which seems redundant since instruction IL_0011 follows immediately regardless of whether or not the result is inequality, yet most likely is used in order to pop off the stack the last two values that still remain on it.

So once again the question becomes, why the discrepancy, regardless of how significant or insignificant it may be. Well, in this case, the answer is clear and simple. For backwards compatibility reasons, VB is forced to use StrCmp instead of String::op_Equality, the results of which are quite different, with the more drastic one being that StrCmp considers an empty string and a Null string equal, which is not the case with String::op_Equality, and, furthermore, this is most likely the main reason why the former is used instead of the latter. From a pure performance standpoint, String::op_Equality is superior to StrCmp, although the performance gains will manifest themselves only when an intense number of string comparisons are made, perhaps those made inside a tight loop. Furthermore, there is an easy workaround which will eliminate this problem in cases where maximum performance is a must, and that is to use String.Equals or op_Equality instead of the = operator when comparing string values. For example, changing the VB version of foo2 to:

Public Sub foo2(ByVal test As Boolean)
    Dim s As String = Nothing
    If String.op_Equality(s, String.Empty) Then
    End If
End Sub

results in the following MSIL code:

.method public instance void  foo2(bool test) cil managed
{
  // Code size       16 (0x10)

  .maxstack  2
  .locals init (string V_0)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  ldsfld     string [mscorlib]System.String::Empty
  IL_0008:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_000d:  brfalse.s  IL_000f
  IL_000f:  ret
} // end of method Class1::foo2

Note that now both versions of the MSIL code are identical, except for the second to last instruction. The C# version simply pops off the stack the last value that remains on it, via the pop instruction, whereas the VB version pops off the last value on the stack, compares it to zero, and if equality exists, transfers control to instruction IL_000f, all via the brfalse.s L_000f instruction, which in this case seems to be redundant, since the original if statement block is empty and therefore the compiler should be smart enough to ignore it, although on the other hand, the compiler is also smart enough to know that under most, if not all, circumstances, an if statement block will not be empty and, therefore, probably figures why even bother trying to detect one.

Significance of Test Results

You probably have come to the conclusion after having read this article that I am a C# programmer who is trying to prove a point. Well, you�re half right. I�m certainly trying to prove a point, and the point is that, for reasons that have to do with the intrinsic nature of the VB language, how you use the language, backwards compatibility with legacy code, and logical assumptions that result in inaction, the VB compiler generates code that is almost as efficient as the C# compiler but does not generate code that is as a efficient as the C# compiler.

(Note: biased statements intended to promote VB vs. C# language wars are about to begin, so you may want to stop reading.)

Having said that, let me make it quite clear that I do all of my imperative coding in VB, ever since I got my first job after having obtained my BS in MIS. It�s an excellent language that, since version 4 and to a greater extent starting with version 7 (.NET), doesn�t restrict the programmer to a single programming paradigm the way C# does. Regarding the significance of the differences in MSIL code this article focused on, who on earth really cares besides programmers that despise (CG) VB? Certainly, the folks that pay us to code could care less about all the statements made in this article, and I assure you that if I were to try to sell the case that C# is �better� than VB based on what�s in this article to a CIO/CTO that knows his .NET, I would probably be fired for my complete lack of business judgment. Furthermore, go and show Jim, VP of finance, the MSIL code that�s included in this article and try to make the case that all of his department�s VB applications should all be rebuilt in C#, and I guarantee you will be the laughing stock of the season, although some C# folks around here, one in particular, are bold enough to try something like this.

Really, the one and only reason I�ve written this article is just to throw gasoline on the flames of the VB vs. C# wars. I�m sick and tired of hearing all the rhetoric about all .NET languages being equal, because that is just not the case, for VB is without question superior to C#. Furthermore, I also pity all the pathetic premises, ones that are so easily negated, that are used to support the absurd argument that C# is "better" than VB. Things like �it�s too wordy� or �too much bad VB code exists� just does not cut it. If you start a C# vs. VB debate here on CP or anywhere, please make sure to give me the link so that I can participate.

Please don't take all my trash talk seriously. I respect all of you guys. It's just that I have zero respect for the C# language. It's just another RAD tool, nothing more, nothing less, that's been fortunate enough to leverage off the experiences, good and bad, of various other languages, including VB, and, furthermore, doesn't have to worry about existing legacy code. My attitude will likely change the day MS Office is written completely in C#.

Final Thoughts

Finally, I'm fairly confident that most of the conclusions I've made regarding the comparison of MSIL code generation between the C# and VB compilers are true. However, like I already mentioned, I'm no expert, yet if you are and know something I don't or was able to detect errors on my part, please let me know.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here