我知道关于Linq及其内部运作的文章很多。受到Jon Skeets 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一样,它在跳跃。但是最后,这只是一个正常的方法,所以我想知道那里到底发生了什么?

最佳答案

每次您对结果调用MoveNext()时,它将:

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


  • 因此,这里没有什么特别神奇的事情-像平常一样,您刚刚接到了很多电话。调试经验将向您显示从一个EnumeratorWrapper.MoveNext到另一个Main的调用。唯一的跳跃是在您逐步浏览ojit_code方法中声明的投影时。

    如果那不能解释什么使您感到困惑,请提供更多有关您不了解流程的确切信息,我将为您提供帮助。 (很高兴看到您想要了解所有这些事情的工作方式。很高兴看到有同情心的人!)

    关于c# - 不使用yield关键字实现Linqs Select。无法遵循控制流程,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8233203/

    10-10 18:27