在没有yield关键字的情况下实现Linqs Select.无法遵循控制流程

Chr*_*oph 5 c# linq iterator yield

我知道很多关于Linq和它的内部运作的文章.受Jon Skeets EduLinq的启发,我想揭开Linq运营商背后的神秘面纱.所以我试图做的是实现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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

它似乎工作.但是,我无法遵循它的控制流程.如你所见,我链接到投影.这导致在MoveNext()方法中发生奇怪的事情(对我来说很奇怪!).如果在MoveNext()方法的每一行中设置断点,您将看到控制流实际上在不同实例之间跳转,并且从不在一个批处理中通过该方法.它正在跳跃,好像它正在使用不同的线程或者如果我们使用yield.但最后,这只是一种常规方法,所以我想知道那里发生了什么?

Jon*_*eet 5

每次调用MoveNext()结果时,它都会:

  • 调用MoveNext()最终的迭代器(我称之为Y),这将...
  • 调用MoveNext()前一个迭代器(我称之为X),这将...
  • 调用MoveNext()原始迭代器(阵列),这将返回一个数.
  • MoveNext()然后在X中调用投影x => x.ToString(),使其具有适当的Current成员
  • MoveNext()然后在Y中调用x => x + "test"结果的投影,并将结果X.Current存储在中Y.Current

所以这里没有什么特别神奇的东西 - 你只是像往常一样有堆叠的电话.调试体验会向您显示从一个EnumeratorWrapper.MoveNext到另一个的呼叫; 当您单步执行在Main方法中声明的投影时,唯一奇怪的跳跃.

如果这并不能说明什么混淆了你,请提供更多的细节了解究竟在那里你不了解流程,我会看我怎么能帮助.(很高兴看到你想要更多地了解所有这些东西是如何工作的.很高兴看到一个志趣相投的精神!)