Ildasm is the Common Intermediate Language (CIL) disassembler ”it parses any .NET assembly and shows a graphical or textual representation of the CIL, name -spaces, types, and interfaces within the assembly. If you want to know what's really going on inside your code, Ildasm is the tool that will tell you. As well as analyzing your own assemblies, you can point Ildasm at assemblies belonging to other developers or even at the .NET Framework assemblies themselves .
The easiest way to check CIL on a regular basis is to create a new item on Visual Studio's Tools menu. You can do this by going to Tools ’ External Tools and completing the External Tools dialog window with the following entries:
Title: CIL (or whatever title you want)
Command: C:\Program Files\Microsoft .Net\FrameworkSDK\bin\ ildasm.exe
Arguments: $(TargetPath) /adv /source
Initial directory: $(TargetDir)
This creates the new item on the Tools menu. Selecting this new item invokes Ildasm in graphical mode on the current assembly, allowing you to dig down to each method and examine the CIL. Notice the two switches that I have chosen to specify. The /adv switch adds some useful extra options to the Ildasm View menu (such as access to statistics), and the /source argument causes source code to be embedded into the CIL display as comments.
Invoking Ildasm from the command line is just as easy. The following command line tells Ildasm to create a disk file named MyAssembly.il containing the CIL extracted from the assembly MyAssembly.exe. Once again, note the use of the /source argument to embed source code as comments within the CIL listing.
Ildasm MyAssembly.exe /source /out=MyAssembly.il
To give you a good idea of Ildasm 's capabilities, I'm going to investigate an example that compares two different VB .NET error-handling techniques. When you wrote VB.Classic code, you were required to use the On Error Goto form of error handling. VB .NET still allows you to keep this legacy method of error handling, but it offers a more modern alternative in the form of Try Catch Finally . Now VB developers have to make decisions about which form of error handling is best, and even whether it is advisable to mix the two modes within an application.
Error handling is a large subject that will be investigated in depth as part of Chapter 13. Here I want to investigate a small subset of the problem. Should you replace the On Error Resume Next method of suppressing unwanted errors with Try Catch Finally ? Listing 5-1 shows the simple program that I'm going to use for experimentation. This program sets up a database connection string and then performs two database connection tests. The first test uses the On Error Resume Next form of error handling to suppress any connection error, clean up the connection, and then return true or false depending on whether the connection was made successfully. The second test is almost identical, but it instead uses the Try Catch Finally form of error handling.
Option Strict On Imports System.Data.SqlClient Module ErrorTest Sub Main () Dim strConnection As String 'Set up database connection strConnection = "Initial Catalog = Northwind;" strConnection += "Data Source = CHEETAH;" strConnection += "Integrated Security = SSPI" 'Try old and new error-handling functions MethodOld(strConnection) MethodNew(strConnection) End Sub Function MethodOld (ByVal ConnectString As String) As Boolean Dim objSqlConnect As SqlConnection 'Test database connection with old error handling On Error Resume Next objSqlConnect = New SqlConnection(ConnectString) MethodOld = CBool(Err.Number = 0) objSqlConnect.Close() objSqlConnect.Dispose() End Function Function MethodNew (ByVal ConnectString As String) As Boolean Dim objSqlConnect As SqlConnection 'Test database connection with new error handling Try objSqlConnect = New SqlConnection(ConnectString) objSqlConnect.Close() objSqlConnect.Dispose() Return True Catch Return False Finally End Try End Function End Module
If you compile this program as a debug build, remembering to replace CHEETAH with your own data source, and then disassemble the resulting executable using Ildasm , it is possible to compare the CIL generated for each of these two methods and make some sort of comparison of their relative efficiency.
Now you can examine the CIL generated for each of the two test methods. As before, the /source flag means that the VB .NET source code is added as comments to the CIL code. Listing 5-2 shows the CIL generated for the legacy method of error handling, and Listing 5-3 shows its more modern counterpart . The VB .NET source lines are highlighted in bold.
.method public static bool MethodOld(string ConnectString) cil managed { // Code size 174 (0xae) .maxstack 2 .locals init ([0] bool MethodOld, [1] class [System.Data]System.Data.SqlClient.SqlConnection objSqlConnect, [2] int32 _Vb_t_CurrentStatement, [3] class [mscorlib]System.Exception _Vb_t_Exception, [4] int32 _Vb_t_Resume, [5] int32 _Vb_t_OnError) //000017: //000018: Function MethodOld(ByVal ConnectString As String) As Boolean IL_0000: nop //000019: Dim objSqlConnect As SqlConnection //000020: //000021: 'Test database connection //000022: On Error Resume Next IL_0001: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices. ProjectData::ClearProjectError() IL_0006: ldc.i4.1 IL_0007: stloc.s _Vb_t_OnError //000023: objSqlConnect = New SqlConnection(ConnectString) IL_0009: ldc.i4.1 IL_000a: stloc.2 IL_000b: ldarg.0 IL_000c: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string) IL_0011: stloc.1 //000024: MethodOld = CBool(Err.Number = 0) IL_0012: ldc.i4.2 IL_0013: stloc.2 IL_0014: call class [Microsoft.VisualBasic]Microsoft.VisualBasic.ErrObject [Microsoft.VisualBasic]Microsoft.VisualBasic.Information::Err() IL_0019: callvirt instance int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.ErrObject::get_ Number() IL_001e: ldc.i4.0 IL_001f: ceq IL_0021: stloc.0 //000025: objSqlConnect.Close() IL_0022: ldc.i4.3 IL_0023: stloc.2 IL_0024: ldloc.1 IL_0025: callvirt instance void [System.Data]System.Data.SqlClient.SqlConnection::Close() IL_002a: nop //000026: objSqlConnect.Dispose() IL_002b: ldc.i4.4 IL_002c: stloc.2 IL_002d: ldloc.1 IL_002e: callvirt instance void [System]System.ComponentModel.Component::Dispose() IL_0033: nop IL_0034: leave.s IL_00a3 IL_0036: ldloc.s _Vb_t_Resume IL_0038: ldc.i4.1 IL_0039: add IL_003a: ldc.i4.0 IL_003b: stloc.s _Vb_t_Resume IL_003d: switch ( IL_0001, IL_0009, IL_0012, IL_0022, IL_002b, IL_0034) IL_005a: leave.s IL_00a1 IL_005c: isinst [mscorlib]System.Exception IL_0061: brtrue.s IL_0065 IL_0063: br.s IL_0070 IL_0065: ldloc.s _Vb_t_OnError IL_0067: brfalse.s IL_0070 IL_0069: ldloc.s _Vb_t_Resume IL_006b: brtrue.s IL_0070 IL_006d: ldc.i4.1 IL_006e: br.s IL_0073 IL_0070: ldc.i4.0 IL_0071: br.s IL_0073 IL_0073: endfilter IL_0075: castclass [mscorlib]System.Exception IL_007a: dup IL_007b: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData:: SetProjectError(class [mscorlib]System.Exception) IL_0080: stloc.3 IL_0081: ldloc.s _Vb_t_Resume IL_0083: brfalse.s IL_0087 IL_0085: leave.s IL_00a1 IL_0087: ldloc.2 IL_0088: stloc.s _Vb_t_Resume IL_008a: ldloc.s _Vb_t_OnError IL_008c: switch ( IL_009b, IL_009d) IL_0099: leave.s IL_009f IL_009b: leave.s IL_009f IL_009d: leave.s IL_0036 IL_009f: rethrow IL_00a1: ldloc.3 .try IL_0001 to IL_005c filter IL_005c handler IL_0075 to IL_00a1 IL_00a2: throw //000027: //000028: End Function IL_00a3: ldloc.0 IL_00a4: ldloc.s _Vb_t_Resume IL_00a6: brfalse.s IL_00ad IL_00a8: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData:: ClearProjectError() IL_00ad: ret } // end of method ErrorTest::MethodOld
.method public static bool MethodNew(string ConnectString) cil managed { // Code size 56 (0x38) .maxstack 1 .locals init ([0] bool MethodNew, [1] class [System.Data]System.Data.SqlClient.SqlConnection objSqlConnect) //000029: //000030: Function MethodNew(ByVal ConnectString As String) As Boolean IL_0000: nop //000031: Dim objSqlConnect As SqlConnection //000032: //000033: 'Test database connection //000034: Try IL_0001: nop //000035: objSqlConnect = New SqlConnection(ConnectString) .try { .try { IL_0002: ldarg.0 IL_0003: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string) IL_0008: stloc.1 //000036: objSqlConnect.Close() IL_0009: ldloc.1 IL_000a: callvirt instance void [System.Data]System.Data.SqlClient.SqlConnection::Close() IL_000f: nop //000037: objSqlConnect.Dispose() IL_0010: ldloc.1 IL_0011: callvirt instance void [System]System.ComponentModel.Component::Dispose() IL_0016: nop //000038: Return True IL_0017: ldc.i4.1 IL_0018: stloc.0 IL_0019: leave. IL_0036 IL_001b: leave.s IL_0035 //000039: Catch } // end.try catch [mscorlib]System.Exception { IL_001d: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices. ProjectData::SetProjectError(class [mscorlib]System.Exception) IL_0022: nop //000040: Return False IL_0023: ldc.i4.0 IL_0024: stloc.0 IL_0025: cal void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices. ProjectData::ClearProjectError() IL_002a: leave.s IL_0036 IL_002c: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices. ProjectData::ClearProjectError() IL_0031: leave.s IL_0035 //000041: Finally } // end handler } // end.try finally { IL_0033: nop IL_0034: endfinally //000042: End Try } // end handler IL_0035: nop //000043: //000044: End Function IL_0036: ldloc.0 IL_0037: ret} // end of method ErrorTest::MethodNew
The most obvious result is that the older form of error handling produces 125 lines of CIL, including 9 comment lines containing VB .NET source. The newer form of error handling generates 76 lines of CIL, including 14 comment lines containing VB .NET source. Although it's true that the more compact (by 40%) CIL may not necessarily mean better or faster code, a deeper investigation of the longer CIL routine shows some rather peculiar gyrations. There are some clear signs that, at least in this particular instance, the more modern form of error handling is better.
As you can see from the preceding experiment, Ildasm is very useful for deeper investigations. When you want to be sure of what the code is really doing under the hood, seeing the CIL can be very instructive. If you want to delve further into CIL itself and how it works, see the "CIL Instruction Set Specification" document, which you can find in the Partition III CIL.doc file in the FrameworkSDK\Tool Developers Guide\Docs folder.