Реализация Linqs Выберите без ключевого слова yield.Не может следить за потоком управления - PullRequest
5 голосов
/ 23 ноября 2011

Я знаю, что много написано о Linq и его внутренней работе. Вдохновленный Джоном Скитсом EduLinq, я хотел рассказать о том, что происходит за Linq Operators. Поэтому я попытался реализовать метод Linqs Select (), который на первый взгляд звучит довольно скучно. Но на самом деле я пытаюсь реализовать это без использования ключевого слова yield.

Итак, вот что я получил:

class Program
{
    static void Main(string[] args)
    {
        var list = new int[] {1, 2, 3};

        var otherList = list.MySelect(x => x.ToString()).MySelect(x => x + "test");

        foreach (var item in otherList)
        {
            Console.WriteLine(item);
        }

        Console.ReadLine();
    }
}

public static class EnumerableEx
{
    public static IEnumerable<R> MySelect<T, R>(this IEnumerable<T> sequence, Func<T, R> apply)
    {
        return new EnumerableWrapper<R, T>(sequence, apply);
    }
}

public class EnumerableWrapper<T, O> : IEnumerable<T>
{
    private readonly IEnumerable<O> _sequence;
    private readonly Func<O, T> _apply;

    public EnumerableWrapper(IEnumerable<O> sequence, Func<O, T> apply)
    {
        _sequence = sequence;
        _apply = apply;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new EnumeratorWrapper<T, O>(_sequence, _apply);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class EnumeratorWrapper<T, O> : IEnumerator<T>
{
    private readonly IEnumerator<O> _enumerator;
    private readonly Func<O, T> _apply;

    public EnumeratorWrapper(IEnumerable<O> sequence, Func<O, T> apply)
    {
        _enumerator = sequence.GetEnumerator();
        _apply = apply;
    }

    public void Dispose()
    {
    }

    public bool MoveNext()
    {
        var hasItems = _enumerator.MoveNext();
        if (hasItems)
            Current = _apply(_enumerator.Current);
        return hasItems;
    }

    public void Reset()
    {
        _enumerator.Reset();
    }

    public T Current { get; private set; }

    object IEnumerator.Current
    {
        get { return Current; }
    }
}

Кажется, работает. Тем не менее, у меня есть проблемы, чтобы следить за его потоком управления. Как видите, я цепляюсь за прогнозы. Это приводит к тому, что странные вещи (странные для меня!) Происходят в методе MoveNext (). Если вы установите точки останова в каждой строке метода MoveNext (), вы увидите, что поток управления фактически переходит между различными экземплярами и никогда не работает через метод в одном пакете. Он прыгает, как если бы он использовал разные потоки или если мы использовали yield. Но, в конце концов, это просто нормальный метод, поэтому мне интересно, что там происходит?

1 Ответ

5 голосов
/ 23 ноября 2011

Каждый раз, когда вы вызываете MoveNext() для результата, он будет:

  • Вызывать MoveNext() на последнем итераторе (который я назову Y), который будет ...
  • Вызов MoveNext() на предыдущем итераторе (который я назову X), который будет ...
  • Вызов MoveNext() на оригинальном итераторе (массива), который вернет число.
  • MoveNext() в X затем вызовет проекцию x => x.ToString(), так что у него есть соответствующий Current член
  • MoveNext() в Y, затем вызоветпроекция x => x + "test" на результат X.Current и сохранение результата в Y.Current

Так что здесь ничего особенного не происходит - вы просто получаете сложенные вызовы, как обычно,Опыт отладки покажет вам этот вызов от одного EnumeratorWrapper.MoveNext к другому;единственный странный прыжок вокруг вас будет, когда вы пройдете через проекции, которые объявлены в методе Main.

Если это не объясняет, что вас смущает, пожалуйста, дайте более подробную информацию о точно где вы не понимаете поток, и я посмотрю, как я могу помочь.(Приятно видеть, что вы хотите узнать больше о том, как все это работает. Приятно видеть родственный дух!)

...