for, foreach, forFirst, forLast

What I really want to do is very much like...

List<int> list = new List<int>(
    new int[6] { 0,1,2,3,4,5 } );        

forFirst(int i in list)
{
    Console.WriteLine("Do something clever with "+i);
}
forAllButLast(int i in theRest)
{
    Console.WriteLine(i);
}
forLast(int i in theRest)
{
    Console.WriteLine("Do something else clever with "+i);
}

But of course that doesn't work, so after reading a post on the development of the C# language to support lambda expressions I wondered how close I can get.

Reconstructing foreach

Before we can change how something works, we need to understand how it works and attempt to replicate it. Here is a very simple foreach

foreach (int i in list)
{
    Console.WriteLine(i);
}

which compiles to the following IL, please note the highlighted sections

    L_0000: nop
    L_0001: ldc.i4.6 
    L_0002: newarr int32
    L_0007: dup 
    L_0008: ldtoken valuetype {B2C01433-68A8-4EFA-ACCD-880F1F4FCBE2}/
__StaticArrayInitTypeSize=24 {B2C01433-68A8-4EFA-ACCD-880F1F4FCBE2}::$$method0x6000001-1 L_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::
InitializeArray(class [mscorlib]System.Array, valuetype [mscorlib]System.RuntimeFieldHandle) L_0012: newobj instance void [mscorlib]System.Collections.Generic.List`1::.ctor(
class [mscorlib]System.Collections.Generic.IEnumerable`1) L_0017: stloc.0 L_0018: ldloc.0 L_0019: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/
Enumerator [
mscorlib]System.Collections.Generic.List`1::GetEnumerator() L_001e: box [mscorlib]System.Collections.Generic.List`1/Enumerator L_0023: stloc.1 L_0024: br.s L_0036 L_0026: nop L_0027: ldloc.1 L_0028: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1::
get_Current()
L_002d: stloc.2 L_002e: ldloc.2 L_002f: call void [mscorlib]System.Console::WriteLine(int32) L_0034: nop L_0035: nop L_0036: ldloc.1 L_0037: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() L_003c: stloc.3 L_003d: ldloc.3 L_003e: brtrue.s L_0026 L_0040: ret

This matches the documentation on the MSDN page, that foreach is most likely to be equivalent to the following code.

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

And that compiles to the following IL

    L_0000: nop 
    L_0001: ldc.i4.6 
    L_0002: newarr int32
    L_0007: dup 
    L_0008: ldtoken valuetype {8373D3B1-DD7F-43BF-A82E-EDDB0C7487BB}/
__StaticArrayInitTypeSize=24 {8373D3B1-DD7F-43BF-A82E-EDDB0C7487BB}::$$method0x6000001-1 L_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::
InitializeArray(class [mscorlib]System.Array, valuetype [mscorlib]System.RuntimeFieldHandle) L_0012: newobj instance void [mscorlib]System.Collections.Generic.List`1::.ctor(
class [mscorlib]System.Collections.Generic.IEnumerable`1) L_0017: stloc.0 L_0018: nop L_0019: ldloc.0 L_001a: callvirt instance valuetype [mscorlib]
System.Collections.Generic.List`1/
Enumerator [mscorlib]System.Collections.Generic.List`1::GetEnumerator()
L_001f: stloc.2 L_0020: br.s L_0033 L_0022: ldloca.s CS$5$0000 L_0024: call instance !0 [mscorlib]System.Collections.Generic.List`1/
Enumerator::get_Current()
L_0029: stloc.1 L_002a: nop L_002b: ldloc.1 L_002c: call void [mscorlib]System.Console::WriteLine(int32) L_0031: nop L_0032: nop L_0033: ldloca.s CS$5$0000 L_0035: call instance bool [mscorlib]System.Collections.Generic.List`1/
Enumerator::MoveNext()
L_003a: stloc.3 L_003b: ldloc.3 L_003c: brtrue.s L_0022 L_003e: leave.s L_004f L_0040: ldloca.s CS$5$0000 L_0042: constrained [mscorlib]System.Collections.Generic.List`1/Enumerator L_0048: callvirt instance void [mscorlib]System.IDisposable::Dispose() L_004d: nop L_004e: endfinally L_004f: nop L_0050: ret .try L_0020 to L_0040 finally handler L_0040 to L_004f

So not quite the same as I seem to be calling the methods defined in List instead of on the interface IEnumerator, but who knows that might actually be quicker as it may reduce the redirect of the virtual method.

Implementing the other for-s

Now we have an IEnumerator we have something that we can use to dive in and read at specific times. Getting the first is easy, just MoveNext() and read Current. However reading the last is harder, with a single direction of access (Enumerator only exposes MoveNext()) the obvious solution is to read all the list, and when I run out of things to read then the last thing I read has to be the last in the list. Scanning through like this would mean I would skip all the items I want between first and last, but it also gives us the opportunity to do something instead of just discard them.  So now we can actually write.

Desired Actual
List<int> list = new List<int>(
    new int[6] { 0,1,2,3,4,5 } );

forFirst(int i in list)
{
    Console.WriteLine(
        "Do something clever with " + i);
}
List<int> list = new List<int>(
    new int[6] { 0,1,2,3,4,5 } );

IEnumerator enumerator = list.GetEnumerator();
if (enumerator.MoveNext())
{
    using (int i = enumerator.Current)
    {
        Console.WriteLine(
             "Do something clever with "+i);
    }
}
forAllButLast(int i in theRest)
{
    Console.WriteLine(i);
}
int previous;
if (enumerator.MoveNext())
{
    previous = enumerator.Current;
    while (enumerator.MoveNext())
    {
        using (int i = previous)
        {
            Console.WriteLine(i);
        }
        previous = enumerator.Current;
    }
forLast(int i in theRest)
{
    Console.WriteLine(
        "Do something else clever with "
       + i); 
}
using (int i = previous)
{
    Console.WriteLine(
        "Do something else clever with "
        + i);
}}

Unfortunately this still doesn't look like what we were trying to achieve, and for that I need to refer to an MSDN article I just can't find at the moment. Oh well, you'll just have to wait for Part II

Comments are closed