Why Does The Compiler Add An Unnecessary Local Variable
c#, cil, compiler-construction
Solution
It seems like a temporary local is created to hold the results of the comparison (i.e. disposable == null).
Trying this example:
class Program
{
static void Main()
{
if (1 == 1) return;
}
}
..is producing the following IL on my box (Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1):
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 1
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ret
}
local V_0 is created, even though it's actually not used. I believe it's an obvious optimisation even for 'non' optimized compilation :) ..or more likely: all necessary code is generated to enable debugging. I don't know how it might be used in a debugging session, but it's my best guess.
When compiled optimized (i.e. Release configuration) I don't see the extra local.
Problem
This C# code: ``` private void LoadAssignments(AssignmentType assignmentType, Collection<Assignment> assignments) { bool flag; DataTable lessons = this.GetResults(assignmentType); try { IEnumerator enumerator = lessons.Rows.GetEnumerator(); try { while (true) { flag = enumerator.MoveNext(); if (!flag) { break; } DataRow row = (DataRow)enumerator.Current; } } finally { IDisposable disposable = enumerator as IDisposable; flag = disposable == null; if (!flag) { disposable.Dispose(); } } } finally { flag = lessons == null; if (!flag) { lessons.Dispose(); } } } ``` produces this CIL (.NET 4) ``` .method private hidebysig instance void LoadAssignments ( valuetype TTReporterCore.AssignmentType assignmentType, class [mscorlib]System.Collections.ObjectModel.Collection`1<valuetype TTReporterCore.Assignment> assignments ) cil managed { .locals init ( [0] bool flag, [1] class [System.Data]System.Data.DataTable lessons, [2] class [mscorlib]System.Collections.IEnumerator enumerator, [3] class [System.Data]System.Data.DataRow row, [4] class [mscorlib]System.IDisposable disposable, [5] bool flag1 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldarg.1 IL_0003: call instance class [System.Data]System.Data.DataTable TTReporterCore.TTReader::GetResults(valuetype TTReporterCore.AssignmentType) IL_0008: stloc.1 .try { IL_0009: nop IL_000a: ldloc.1 IL_000b: callvirt instance class [System.Data]System.Data.DataRowCollection [System.Data]System.Data.DataTable::get_Rows() IL_0010: callvirt instance class [mscorlib]System.Collections.IEnumerator [System.Data]System.Data.InternalDataCollectionBase::GetEnumerator() IL_0015: stloc.2 .try { IL_0016: nop IL_0017: br.s IL_0038 .loop { IL_0019: nop IL_001a: ldloc.2 IL_001b: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() IL_0020: stloc.0 IL_0021: ldloc.0 IL_0022: stloc.s flag1 IL_0024: ldloc.s flag1 IL_0026: brtrue.s IL_002b IL_0028: nop IL_0029: br.s IL_003d IL_002b: ldloc.2 IL_002c: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() IL_0031: castclass [System.Data]System.Data.DataRow IL_0036: stloc.3 IL_0037: nop IL_0038: ldc.i4.1 IL_0039: stloc.s flag1 IL_003b: br.s IL_0019 } IL_003d: nop IL_003e: leave.s IL_0062 } finally { IL_0040: nop IL_0041: ldloc.2 IL_0042: isinst [mscorlib]System.IDisposable IL_0047: stloc.s disposable IL_0049: ldloc.s disposable IL_004b: ldnull IL_004c: ceq IL_004e: stloc.0 IL_004f: ldloc.0 IL_0050: stloc.s flag1 IL_0052: ldloc.s flag1 IL_0054: brtrue.s IL_0060 IL_0056: nop IL_0057: ldloc.s disposable IL_0059: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_005e: nop IL_005f: nop IL_0060: nop IL_0061: endfinally } IL_0062: nop IL_0063: nop IL_0064: leave.s IL_007e } finally { IL_0066: nop IL_0067: ldloc.1 IL_0068: ldnull IL_0069: ceq IL_006b: stloc.0 IL_006c: ldloc.0 IL_006d: stloc.s flag1 IL_006f: ldloc.s flag1 IL_0071: brtrue.s IL_007c IL_0073: nop IL_0074: ldloc.1 IL_0075: callvirt instance void [System]System.ComponentModel.MarshalByValueComponent::Dispose() IL_007a: nop IL_007b: nop IL_007c: nop IL_007d: endfinally } IL_007e: nop IL_007f: ret } ``` Why does the MSIL add the flag1, continue to perform the same logic to set flag, set flag1 to flag and finally, check for !flag1. This seems like a compiler inefficiency to me. UPDATE: I was using Telerik's JustDecompile and while the results are quite different from ILDASM, the additional boolean is still created in debug mode. Also, I modified the code by removing the boolean completely and the debug version still adds a boolean. I'm really looking for why the compiler does this.