Перечисление по 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, но это уникальная проблема.