Перечисление по lambdas не правильно привязывает область действия?

рассмотрим следующую программу С#:

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();
    }
}

Когда выполняется в Mono-компиляторе (например, Mono 2.10.2.0 - вставляем в здесь), он записывает следующий вывод:

0
1
1
1

Это кажется мне совершенно нелогичным. При прямом итерации функции yield, область действия for-loop "правильно" (к моему пониманию) используется. Но когда я сначала храню результат в списке, область действия всегда является последним действием?!

Можно ли предположить, что это ошибка в компиляторе Mono, или я попал в загадочный угловой случай С# лямбда и урожайности?

BTW: при использовании компилятора Visual Studio (и для выполнения MS.NET или моно) результатом является ожидаемый 0 1 0 1

Ответ 1

Я дам вам причину, по которой это было 0 1 1 1:

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

Здесь вы попадаете в Get и запускаете повторение:

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

yield возвращается с функцией и выполняет функцию, печатает 0 на экране, а затем возвращается к методу Get() и продолжает.

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

yield возвращает с функцией и выполняет функцию, печатает 1 на экране, а затем возвращается к методу Get() и продолжает (только чтобы найти, что он должен остановиться).

Но теперь вы не выполняете итерацию по каждому элементу, когда это происходит, вы создаете список и затем итерации по этому списку.

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

То, что вы делаете, не так, как указано выше, Get().ToList() возвращает список или массив (не уверенный). Итак, теперь это происходит:

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

И в вашей функции Main() вы получите следующее в памяти:

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

Вы возвращаетесь к функции Get():

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

Возврат к Main()

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

И затем делает

foreach (var a in list) a();

Будет распечатан 1 1

Кажется, что он игнорировал, что вы убедились, что вы инкапсулировали значение перед возвратом функции.

Ответ 2

@Armaron - расширение .ToList() возвращает список типа T, поскольку ToArray() возвращает T [], как предполагает соглашение об именах, но я думаю, что вы на правильном пути с ответом.

Это похоже на issuse с компилятором. Я согласен с Servy в том, что, вероятно, это ошибка, вы пробовали следующее?

public class Test
{
    private static int capture = 0;    

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

Кроме того, вы можете попробовать статический подход, возможно, это будет выполнять более точное преобразование, поскольку ваша функция статична.

List<T> list = Enumerable.ToList(Get()); 

При вызове ToList() кажется, что он не выполняет одиночную итерацию для каждого значения, а скорее:

return new List<T>(Get());

Второе для каждого в вашем коде не имеет для меня смысла в реализации того, почему это когда-либо было необходимо или полезно, если вам не потребуются дополнительные действия для добавления/удаления объекта List. Первое имеет прекрасный смысл, поскольку все, что вы делаете, - это итерация через объект и выполнение связанного действия. Мое понимание заключается в том, что целое число в пределах объекта статического объекта IEnumerbale вычисляется во время преобразования, выполняя всю итерацию, и действие сохраняет int как статический int из-за области видимости. Кроме того, имейте в виду, что IEnumerable - это просто интерфейс, который реализуется List, который реализует IList и может содержать логику для встроенного преобразования.

Как мне сказали, мне интересно посмотреть/услышать ваши выводы, так как это интересный пост. Я определенно буду поднимать вопрос. Пожалуйста, задавайте вопросы, если что-либо, что я сказал, нуждается в разъяснении или если что-то ложно, скажите так, хотя я уверен в своем использовании ключевого слова yield в IEnumerable, но это уникальная проблема.