Click here to Skip to main content
15,888,233 members
Articles / Programming Languages / MSIL
Article

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

Rate me:
Please Sign up or sign in to vote.
4.60/5 (38 votes)
7 Feb 200511 min read 288.6K   322   32   106
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:

VB
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:

C#
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):

MSIL
.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):

MSIL
.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.

VB
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:

MSIL
.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):

MSIL
.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):

MSIL
.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:

VB
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:

MSIL
.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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralBefore compare, you must KNOW how to program PinPopular
JCKodel27-Jun-09 13:31
JCKodel27-Jun-09 13:31 
GeneralSimilar result Pin
Fco. Javier Marin17-Nov-08 11:15
Fco. Javier Marin17-Nov-08 11:15 
GeneralWho really cares if vb code is less efficient.... Pin
LongRange.Shooter10-Jul-08 5:13
LongRange.Shooter10-Jul-08 5:13 
GeneralThere is one additional situation that point to even less efficiencies Pin
LongRange.Shooter10-Jul-08 5:03
LongRange.Shooter10-Jul-08 5:03 
GeneralBuild mode Pin
Fiorentino18-May-08 8:01
Fiorentino18-May-08 8:01 
GeneralRevival of the battle! Pin
pc.huff19-Apr-07 3:34
pc.huff19-Apr-07 3:34 
GeneralMore fuel for the flames! Pin
Rei Miyasaka18-Apr-05 2:09
Rei Miyasaka18-Apr-05 2:09 
GeneralThe extra local will be for return type Pin
vinay_chaudhari1-Mar-05 1:47
vinay_chaudhari1-Mar-05 1:47 
GeneralLanguage Edge of C#.net over VB.Net Pin
Misty_Blue24-Feb-05 8:58
Misty_Blue24-Feb-05 8:58 
GeneralRe: Language Edge of C#.net over VB.Net Pin
Giancarlo Aguilera24-Feb-05 12:20
Giancarlo Aguilera24-Feb-05 12:20 
GeneralRe: Language Edge of C#.net over VB.Net Pin
Misty_Blue24-Feb-05 16:29
Misty_Blue24-Feb-05 16:29 
GeneralRe: Language Edge of C#.net over VB.Net Pin
Giancarlo Aguilera24-Feb-05 20:21
Giancarlo Aguilera24-Feb-05 20:21 
GeneralRe: Language Edge of C#.net over VB.Net Pin
Misty_Blue26-Feb-05 4:27
Misty_Blue26-Feb-05 4:27 
GeneralRe: Language Edge of C#.net over VB.Net Pin
Dave Bacher2-May-05 12:14
Dave Bacher2-May-05 12:14 
Coming at this from someone who uses both languages, and has used VB since 1.0...

Giancarlo Aguilera wrote:
First of all, I think it's fantastic that a single language can be used as both a loosely typed and strongly typed language, and that it can be controlled at the file level. Furthemore, I also believe it's powerful that a single language gives you the ability to declare or not declare your variables before using them, and that it can be controlled at the file level.

Actually, I see this as one of VB's greatest weaknesses, and a reason why it isn't being taken seriously by many non-VB developers. When discussing a function or subroutine out of context, or at a formalized code review, you can't see how these options are declared at a file level, and so information that you need to know if the implementation is correct or not isn't there.

Option Explicit should not be supported in VB.NET. It was a mistake in VB 6, and it was a mistake in all previous versions, but in VB.NET (in particular) it should have been dropped outright.

Late binding is the only marginally acceptable use, and Microsoft could easily introduce a keyword or operator to support late binding.

The issue with Option Explicit off is it causes numerous hard to track bugs in an application of any moderate complexity, and it makes it impossible to deal with a function out of context (at a peer review, etc.).

Sub DoSomething()
Offset = 0
Do While Not Done
Offset = Offset + 1
Done = True
End While
End Sub

Is Offset class local? Is it sub local? Is it global (in VB6-)?

Does the line incrementing it set it to 1, or 01? If the loop ran multiple times, would it increment normally or would it end up being 01111111111?

You can't tell any of this from looking at the sub in isolation, and that is a barrier to communicating with other developers. It is also a barrier to effectively catching problems before code ships, which is traditionally what earned VB it's low reputation among other programmers.


Giancarlo Aguilera wrote:
Are you kidding me? The VB Select Case statement is much more powerful than C#'s switch! You can specify a list of values, a range of values, or even both, or you can test using relational operators, etc... How do you do this in C#:

The C# approach lends itself well to things like interpreters, while the VB approach lends itself to certain varieties of data validation. Generally speaking, break is one of the statements in C++ and C# that causes the most odd, untrackable bugs (because people forget to use it a lot).

Both approaches have merits in different situations. Personally, I dislike break and think it should be removed from the C# language as well. Break doesn't get you anything a function wouldn't, and the function makes your intent crystal clear.

But then I think C# and VB.NET both should get inner functions and inner subs in order to promote clear, readable code -- so what do I know...

Giancarlo Aguilera wrote:
Oh please ! First of all, line continuation is acheived with just the underscore and not the ampersand. Second, I would rather have to use the line continuation character on the few occassions that a code line is too long for a single line rather than have to type in a semicolon for every single line of code I write .

VB needs an equivalent of /* ... */ comments, which is where this complaint usually originates:
void Func(
int param1, // This parameter is for (blah)
int param2 // This paramerter is for (blah)
) {..}

In VB.NET, you can't do anything equivalent:
Sub Func( _
param1 As Integer, _ 'This won't compile with a comment here
param2 As Integer _ 'This won't compile with a comment here
)

If you look at were continuations are useful, it's almost always a sub/function declaration or an expression. If you allowed ( to auto continue until there was a matching ), without continuation characters, you could also address both complaints. But that won't happen anytime soon.


Giancarlo Aguilera wrote:
First of all C#'s array also derives from System.Array, so they're one in the same with VB's. Second, ReDim Preserve is just a VB specific way of calling Array.CopyTo, which is what you would do if you want to increase the size of an array yet keep its current values, in the end you still have to create another one. Regardless of how you do it, yes I know it's a performance killer.

If you're talking like a game loop doing pixel manipulation or the like, in a C# unsafe block, it's possible to use a native array. It still doesn't perform as well as the equivalent C++ code, and it requires the full trust profile (which can be annoying) since it uses unsafe, but it can be done.


Giancarlo Aguilera wrote:
5) C# uses PInvokewhich allows you to call functions and have data types be marshaled correctly which is much better than VB.NEt's "Declare " to call Windows API."

So does VB! Sure you have the option to use Declare, in addition to DLLImport, but in the end all Declare statements are replaced with DLLImport. Once again, it's all about options .


Declare should generate a deprecated warning, in fact (it doesn't, but it should), because it's actually intended to be an aid in porting old code, versus something for new use. You open any VB 6 code that does anything with Windows, and Declare is the first line you'll see. There had to be a migration path, but they should inform you its a migration path and steer you in the right direction.


Giancarlo Aguilera wrote:
"8) VB.Net allows for a lot of implicit casting while C# requires casts to be explicit (Thank God!!)"

No doubt, but it ALSO can funtion in explicit mode as well (option strict on baby ) It's all about options!


Option Strict is another one that should be locked on. Most programming shops I've worked at the last 10 years have required it to be on for all code. It's another one that was originally intended to ease migration from GW/Q BASIC that has served its purpose and should be out of there.

To support late binding, there should be a specific language pseudo-type or a modifier for System.Object:
Dim A As LateBound System.Object


Giancarlo Aguilera wrote:
Misty_Blue wrote:
"9) When computing for checksum or hash on a class, the behavior of C# of not checking arithmatic overflow is reaaly helpful and then it provides "checked" and "unchecked" keywords."

VB also allows you to disable overflow. No we don't have checked and unchecked, but I don't care


Checked and unchecked are necessary, in rare cases, for performance.

In the case of the stated applications, though, I would typically use the C++ implementation of checksums or hashes in System.Security.Cryptography; even with unchecked, running in an unsafe block, you're unlikely to beat the implementations there for performance.

Microsoft omitted anything from VB.NET that is intended for use by low-level processes, where performance is typically critical. In C#, they put these capabilities in; in C++, they were standard. Any time you get into a feature that would benefit from low-level support, C# is a better fit than VB.NET. It's not that you can't manipulate an array in VB.NET, for example. It's that you can't do it as fast as in C# can in an unsafe block.

In general, option explicit off and option strict off will cripple performance. It's not that the process won't run, or won't compile. It's that every 2nd or 3rd line of code is boxing/unboxing. With implicit conversions, you have overhead at every assign, above and beyond box/unbox.

Generally speaking, my opinion is that Microsoft should have said "sorry, not supported anymore." Most apps require significant work to port anyway, so backwards compatibility didn't need to be a concern.
GeneralRe: Language Edge of C#.net over VB.Net Pin
Giancarlo Aguilera2-May-05 14:17
Giancarlo Aguilera2-May-05 14:17 
GeneralRe: Language Edge of C#.net over VB.Net Pin
Dag Oystein Johansen20-Aug-07 7:51
Dag Oystein Johansen20-Aug-07 7:51 
GeneralRe: Language Edge of C#.net over VB.Net Pin
Jonathan C Dickinson22-Jul-08 1:23
Jonathan C Dickinson22-Jul-08 1:23 
QuestionAre you really coding in OO Style? Pin
IanXiao23-Feb-05 19:03
IanXiao23-Feb-05 19:03 
AnswerRe: Are you really coding in OO Style? Pin
S. Senthil Kumar23-Feb-05 19:52
S. Senthil Kumar23-Feb-05 19:52 
AnswerRe: Are you really coding in OO Style? Pin
Giancarlo Aguilera24-Feb-05 6:36
Giancarlo Aguilera24-Feb-05 6:36 
GeneralRe: Are you really coding in OO Style? Pin
Misty_Blue24-Feb-05 9:02
Misty_Blue24-Feb-05 9:02 
AnswerRe: Are you really coding in OO Style? Pin
Dag Oystein Johansen20-Aug-07 8:26
Dag Oystein Johansen20-Aug-07 8:26 
GeneralLanguage War.... Pin
Misty_Blue22-Feb-05 9:14
Misty_Blue22-Feb-05 9:14 
GeneralRe: Language War.... Pin
Giancarlo Aguilera22-Feb-05 9:51
Giancarlo Aguilera22-Feb-05 9:51 
GeneralRe: Language War.... Pin
Misty_Blue22-Feb-05 10:06
Misty_Blue22-Feb-05 10:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.