метод возврата дохода оптимизирован, когда это не должно быть - PullRequest
0 голосов
/ 11 декабря 2018

Я наткнулся на тестовую проблему моей библиотеки, где yield return оптимизирован по причинам, которые я не понимаю.

Подтверждение кода концепции:

static IEnumerable<int> Sample(int count)
{
    for (int i = 0; i < count; i++) yield return i;
}
static IEnumerable<int> ForEach(IEnumerable<int> items, Action<int> action)
{
    foreach (int item in items) { action(item); yield return item; }
}
static void After(IEnumerable<int> items, Action action)
{
    action();
}

static void Main(string[] args)
{
    int item = -1;
    After(ForEach(Sample(10), v => item = v), () => Console.WriteLine(item));
    Console.WriteLine(item);
    Console.ReadKey();
}

Iожидаем, что выходное значение будет 9, затем 9.

Фактическое значение будет -1, затем -1.

Инициализация IEnumerable с при Sample и ForEach оптимизирован, и как побочный эффект item внутри Main никогда не изменяется.

Почему Sample не был повторен в ForEach?

Я? действительно требуется для итерации items в After?

Если да, почему оптимизация была настолько глубокой, что даже итерация в ForEach останавливается до тех пор, пока не будет спровоцирована, что приведет к выводу-1 тогда 9 тоже возможно?

1 Ответ

0 голосов
/ 11 декабря 2018

IEnumerable<T> объекты имеют отложенное выполнение, что означает, что они выполняются, когда к ним обращаются для итерации.Когда мы начинаем итерацию или материализацию объекта этого объекта в List<T> или Array, тогда он фактически вызывается.

Если вы добавите вызов ToList() в свой метод ForEach, вы увидите ожидаемый результат:

After(ForEach(Sample(10), v => item = v).ToList(), () => Console.WriteLine(item));

Как вы видели, вы материализовали результирующий метод ForEach() IEnumerable, он также вызовет Sample() и выполнит.

Вы можете изменить ваши методы, как показано ниже, чтобы увидетькогда на самом деле метод был вызван:

static IEnumerable<int> Sample(int count)
{
    Console.WriteLine("Sample Invoked");
    for (int i = 0; i < count; i++)
        yield return i;
}

static IEnumerable<int> ForEach(IEnumerable<int> items, Action<int> action)
{
    Console.WriteLine("ForEach Invoked:");
    foreach (int item in items)
    {
        action(item);
        yield return item;
    }
}

, а затем вызвать его следующим образом:

int item = -1;
After(ForEach(Sample(10), v => item = v), () => Console.WriteLine(item));
Console.WriteLine(item);

After(ForEach(Sample(10), v => item = v).ToList(), () => 
Console.WriteLine(item));
Console.WriteLine(item);

и получится:

-1

-1

Активировано ForEach:

Вызван образец

9

9

См. Fiddle DEMO чтобы наблюдать за ним в действии.

...