Reason #458 Why Visual Basic.NET Sucks
I wrote the following console application to verify that there was a difference:
Option Strict On
Imports System.Security
Namespace Playground
Public Module VBPlayground
<SuppressUnmanagedCodeSecurity()> _
Private Declare Auto Function QueryPerformanceCounter Lib "kernel32.dll" (ByRef lpPerformanceCount As Long) As Boolean
<SuppressUnmanagedCodeSecurity()> _
Private Declare Auto Function QueryPerformanceFrequency Lib "kernel32.dll" (ByRef lpFrequency As Long) As Boolean
Private m_lFreq As Long
Public Sub Main()
Dim lStart As Long
Dim lStop As Long
QueryPerformanceFrequency(m_lFreq)
Dim strOne As String = "One"
Dim strTwo As String = "Two"
QueryPerformanceCounter(lStart)
Dim bEqualsOp As Boolean = (strOne = strTwo)
QueryPerformanceCounter(lStop)
WriteOutTime(lStart, lStop, "Equals Operator")
QueryPerformanceCounter(lStart)
Dim bEqualsFunc As Boolean = (strOne.Equals(strTwo))
QueryPerformanceCounter(lStop)
WriteOutTime(lStart, lStop, "Equals Function")
System.Console.Read()
End Sub
Private Sub WriteOutTime(ByVal lStart As Long, ByVal lStop As Long, ByVal strMsg As String)
Dim lDiff As Long = lStop - lStart
Dim lTime As Double = lDiff / m_lFreq
Console.WriteLine(String.Format("{0}: {1} sec", strMsg, lTime))
End Sub
End Module
End Namespace
So what are the results you ask? They were surprising... at least to me:
Equals Operator: 0.00150214622249476 sec
Equals Function: 1.9555558038801E-06 sec
That's a difference of two orders of magnitude! What on Earth could be going on under the hood to be causing this? So I continued my investigation by looking at my test program in ILDASM to see what IL code was emitted by the compiler. Here's where it got interesting:
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 116 (0x74)
.maxstack 3
.locals init ([0] bool bEqualsFunc,
[1] bool bEqualsOp,
[2] int64 lStart,
[3] int64 lStop,
[4] string strOne,
[5] string strTwo)
IL_0000: nop
IL_0001: ldsflda int64 Playground.VBPlayground::m_lFreq
IL_0006: call bool Playground.VBPlayground::QueryPerformanceFrequency(int64&)
IL_000b: pop
IL_000c: ldstr "One"
IL_0011: stloc.s strOne
IL_0013: ldstr "Two"
IL_0018: stloc.s strTwo
IL_001a: ldloca.s lStart
IL_001c: call bool Playground.VBPlayground::QueryPerformanceCounter(int64&)
IL_0021: pop
IL_0022: ldloc.s strOne
IL_0024: ldloc.s strTwo
IL_0026: ldc.i4.1
IL_0027: call int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.StringType::StrCmp(string,
string,
bool)
IL_002c: ldc.i4.0
IL_002d: ceq
IL_002f: stloc.1
IL_0030: ldloca.s lStop
IL_0032: call bool Playground.VBPlayground::QueryPerformanceCounter(int64&)
IL_0037: pop
IL_0038: ldloc.2
IL_0039: ldloc.3
IL_003a: ldstr "Equals Operator"
IL_003f: call void Playground.VBPlayground::WriteOutTime(int64,
int64,
string)
IL_0044: nop
IL_0045: ldloca.s lStart
IL_0047: call bool Playground.VBPlayground::QueryPerformanceCounter(int64&)
IL_004c: pop
IL_004d: ldloc.s strOne
IL_004f: ldloc.s strTwo
IL_0051: callvirt instance bool [mscorlib]System.String::Equals(string)
IL_0056: stloc.0
IL_0057: ldloca.s lStop
IL_0059: call bool Playground.VBPlayground::QueryPerformanceCounter(int64&)
IL_005e: pop
IL_005f: ldloc.2
IL_0060: ldloc.3
IL_0061: ldstr "Equals Function"
IL_0066: call void Playground.VBPlayground::WriteOutTime(int64,
int64,
string)
IL_006b: nop
IL_006c: call int32 [mscorlib]System.Console::Read()
IL_0071: pop
IL_0072: nop
IL_0073: ret
} // end of method VBPlayground::Main
Holy cow! This means that if you use the equals operator... you're actually calling into old VB6 libraries using COM Interop. When this occurs, you get a major lag because you have to marshal the string out of the managed memory into unmanaged memory. Using the .Equals method on the other hand keeps everything in managed memory and does a much faster comparison. That explains the difference. My question is... Who was the absolute moron on the VB.NET compiler team who made that decision?
Of course I couldn't stop there. I had to write the same program in C# in order to verify that they don't do anything nearly as stupid. Here is the same program in C#... I believe I got them as close as humanly possible to keep the test valid:
using System;
using System.Security;
using System.Runtime.InteropServices;
namespace Playground
{
class CSPlayground
{
[SuppressUnmanagedCodeSecurity, DllImport( "kernel32.dll" )]
private static extern bool QueryPerformanceCounter( ref long lpPerformanceCount );
[SuppressUnmanagedCodeSecurity, DllImport( "kernel32.dll" )]
private static extern bool QueryPerformanceFrequency( ref long lpFrequency );
private static long m_lFreq = 0;
[STAThread]
public static void Main()
{
long lStart = 0;
long lStop = 0;
QueryPerformanceFrequency( ref m_lFreq );
string strOne = "One";
string strTwo = "Two";
QueryPerformanceCounter( ref lStart );
bool bEqualsOp = ( strOne == strTwo );
QueryPerformanceCounter( ref lStop );
WriteOutTime( lStart, lStop, "Equals Operator" );
QueryPerformanceCounter( ref lStart );
bool bEqualsFunc = ( strOne.Equals( strTwo ) );
QueryPerformanceCounter( ref lStop );
WriteOutTime( lStart, lStop, "Equals Function" );
System.Console.Read();
}
private static void WriteOutTime( long lStart, long lStop, string strMsg )
{
long lDiff = lStop - lStart;
double lTime = (double)lDiff / m_lFreq;
Console.WriteLine( String.Format( "{0}: {1} sec", strMsg, lTime ) );
}
}
}
Here is the output of this program:
Equals Operator: 9.77777901940051E-06 sec
Equals Function: 2.23492091872012E-06 sec
Aha! Nick... there is a difference there too. The operator takes about 4 times longer than the .Equals method. True... but at least its not 2 hundred times different like with VB.NET. So why is there a difference with C#? Here is the IL for this program:
.method public hidebysig static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 110 (0x6e)
.maxstack 3
.locals init ([0] int64 lStart,
[1] int64 lStop,
[2] string strOne,
[3] string strTwo,
[4] bool bEqualsOp,
[5] bool bEqualsFunc)
IL_0000: ldc.i4.0
IL_0001: conv.i8
IL_0002: stloc.0
IL_0003: ldc.i4.0
IL_0004: conv.i8
IL_0005: stloc.1
IL_0006: ldsflda int64 Playground.CSPlayground::m_lFreq
IL_000b: call bool Playground.CSPlayground::QueryPerformanceFrequency(int64&)
IL_0010: pop
IL_0011: ldstr "One"
IL_0016: stloc.2
IL_0017: ldstr "Two"
IL_001c: stloc.3
IL_001d: ldloca.s lStart
IL_001f: call bool Playground.CSPlayground::QueryPerformanceCounter(int64&)
IL_0024: pop
IL_0025: ldloc.2
IL_0026: ldloc.3
IL_0027: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_002c: stloc.s bEqualsOp
IL_002e: ldloca.s lStop
IL_0030: call bool Playground.CSPlayground::QueryPerformanceCounter(int64&)
IL_0035: pop
IL_0036: ldloc.0
IL_0037: ldloc.1
IL_0038: ldstr "Equals Operator"
IL_003d: call void Playground.CSPlayground::WriteOutTime(int64,
int64,
string)
IL_0042: ldloca.s lStart
IL_0044: call bool Playground.CSPlayground::QueryPerformanceCounter(int64&)
IL_0049: pop
IL_004a: ldloc.2
IL_004b: ldloc.3
IL_004c: callvirt instance bool [mscorlib]System.String::Equals(string)
IL_0051: stloc.s bEqualsFunc
IL_0053: ldloca.s lStop
IL_0055: call bool Playground.CSPlayground::QueryPerformanceCounter(int64&)
IL_005a: pop
IL_005b: ldloc.0
IL_005c: ldloc.1
IL_005d: ldstr "Equals Function"
IL_0062: call void Playground.CSPlayground::WriteOutTime(int64,
int64,
string)
IL_0067: call int32 [mscorlib]System.Console::Read()
IL_006c: pop
IL_006d: ret
} // end of method CSPlayground::Main
You have to remember that in .NET, all operators are actually static methods. So when you are using the = operator, it has to probably do a cast or two, then it calls the .Equals method on one of them, which incurrs a bit of overhead to push items on the stack. That probably would be enough to account for the small difference in times.
However at least my faith was reaffirmed that C# is far superior to VB.NET. But the lesson was learned very well. Always use the .Equals method on strings in VB.NET.
Update: Read the comments for more information, and my responses...