Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enumerating over lambdas does not bind the scope correctly?

consider the following C# program:

using System;
using System.Linq;
using System.Collections.Generic;

public class Test
{
    static IEnumerable<Action> Get()
    {
        for (int i = 0; i < 2; i++)
        {
            int capture = i;
            yield return () => Console.WriteLine(capture.ToString());
        }
    }

    public static void Main(string[] args)
    {
        foreach (var a in Get()) a();
        foreach (var a in Get().ToList()) a();
    }
}

When executed under Mono compiler (e.g. Mono 2.10.2.0 - paste into here), it writes the following output:

0
1
1
1

This seems totally unlogical to me. When directly iterating the yield function, the scope of the for-loop is "correctly" (to my understanding) used. But when I store the result in a list first, the scope is always the last action?!

Can I assume that this is a bug in the Mono compiler, or did I hit a mysterious corner case of C#'s lambda and yield-stuff?

BTW: When using Visual Studio compiler (and either MS.NET or mono to execute), the result is the expected 0 1 0 1

like image 625
Imi Avatar asked Mar 05 '14 14:03

Imi


1 Answers

I'll give you the reason why it was 0 1 1 1:

foreach (var a in Get()) a();

Here you go into Get and it starts iterating:

i = 0 => return Console.WriteLine(i);

The yield returns with the function and executes the function, printing 0 to the screen, then returns to the Get() method and continues.

i = 1 => return Console.WriteLine(i);

The yield returns with the function and executes the function, printing 1 to the screen, then returns to the Get() method and continues (only to find that it has to stop).

But now, you're not iterating over each item when it happens, you're building a list and then iterating over that list.

foreach (var a in Get().ToList()) a();

What you are doing isn't like above, Get().ToList() returns a List or Array (not sure wich one). So now this happens:

i = 0 => return Console.WriteLine(i);

And in you Main() function, you get the following in memory:

var i = 0;
var list = new List
{
    Console.WriteLine(i)
}

You go back into the Get() function:

i = 1 => return Console.WriteLine(i);

Which returns to your Main()

var i = 1;
var list = new List
{
    Console.WriteLine(i),
    Console.WriteLine(i)
}

And then does

foreach (var a in list) a();

Which will print out 1 1

It seems like it was ignoring that you made sure you encapsulated the value before returning the function.

like image 193
Ken Bonny Avatar answered Oct 11 '22 21:10

Ken Bonny