为什么收益率与常规可枚举的行为不同?

时间:2012-12-04 12:33:03

标签: c# .net iterator

当我通过比较来制作一个逻辑来迭代两个不同类型的枚举时,我发现了这个:

class Program
{
    public static IEnumerable<mm> YieldlyGet()
    {
        yield return new mm { Int = 0 };
        yield return new mm { Int = 1 };
        yield return new mm { Int = 2 };
        yield return new mm { Int = 3 };
        yield return new mm { Int = 4 };
        yield return new mm { Int = 5 };
    }

    public static IEnumerable<int> YieldlyGetInt()
    {
        yield return 0;
        yield return 1;
        yield return 2;
        yield return 3;
        yield return 4;
        yield return 5;
    }

    public static IEnumerable<int> Get() 
    {
        return new List<int> { 0, 1,2,3,4,5 };
    }

    static void Main(string[] args) 
    {
        var yieldr = YieldlyGet().GetEnumerator();
        var yieldv = YieldlyGetInt().GetEnumerator();

        var list = Get().GetEnumerator();

        int i = -1;
        Console.WriteLine("For the current index: {0}", ++i);
        Console.WriteLine("y-r: Should I move next? {0}, if yes, value: {1}", yieldr.MoveNext(), yieldr.Current != null ? yieldr.Current.Int : 0);
        Console.WriteLine("y-v: Should I move next? {0}, if yes, value: {1}", yieldv.MoveNext(), yieldv.Current);
        Console.WriteLine("l: Should I move next? {0}, if yes, value: {1}", list.MoveNext(), list.Current);

        Console.WriteLine("For the current index: {0}", ++i);
        Console.WriteLine("y-r: Should I move next? {0}, if yes, value: {1}", yieldr.MoveNext(), yieldr.Current != null ? yieldr.Current.Int : 0);
        Console.WriteLine("y-v: Should I move next? {0}, if yes, value: {1}", yieldv.MoveNext(), yieldv.Current);
        Console.WriteLine("l: Should I move next? {0}, if yes, value: {1}", list.MoveNext(), list.Current);

        Console.WriteLine("For the current index: {0}", ++i);
        Console.WriteLine("y-r: Should I move next? {0}, if yes, value: {1}", yieldr.MoveNext(), yieldr.Current != null ? yieldr.Current.Int : 0);
        Console.WriteLine("y-v: Should I move next? {0}, if yes, value: {1}", yieldv.MoveNext(), yieldv.Current);
        Console.WriteLine("l: Should I move next? {0}, if yes, value: {1}", list.MoveNext(), list.Current);

        Console.WriteLine("For the current index: {0}", ++i);
        Console.WriteLine("y-r: Should I move next? {0}, if yes, value: {1}", yieldr.MoveNext(), yieldr.Current != null ? yieldr.Current.Int : 0);
        Console.WriteLine("y-v: Should I move next? {0}, if yes, value: {1}", yieldv.MoveNext(), yieldv.Current);
        Console.WriteLine("l: Should I move next? {0}, if yes, value: {1}", list.MoveNext(), list.Current);

        Console.WriteLine("For the current index: {0}", ++i);
        Console.WriteLine("y-r: Should I move next? {0}, if yes, value: {1}", yieldr.MoveNext(), yieldr.Current != null ? yieldr.Current.Int : 0);
        Console.WriteLine("y-v: Should I move next? {0}, if yes, value: {1}", yieldv.MoveNext(), yieldv.Current);
        Console.WriteLine("l: Should I move next? {0}, if yes, value: {1}", list.MoveNext(), list.Current);

        Console.WriteLine("For the current index: {0}", ++i);
        Console.WriteLine("y-r: Should I move next? {0}, if yes, value: {1}", yieldr.MoveNext(), yieldr.Current != null ? yieldr.Current.Int : 0);
        Console.WriteLine("y-v: Should I move next? {0}, if yes, value: {1}", yieldv.MoveNext(), yieldv.Current);
        Console.WriteLine("l: Should I move next? {0}, if yes, value: {1}", list.MoveNext(), list.Current);

        Console.WriteLine("For the current index: {0}", ++i);
        Console.WriteLine("y-r: Should I move next? {0}, if yes, value: {1}", yieldr.MoveNext(), yieldr.Current != null ? yieldr.Current.Int : 0);
        Console.WriteLine("y-v: Should I move next? {0}, if yes, value: {1}", yieldv.MoveNext(), yieldv.Current);
        Console.WriteLine("l: Should I move next? {0}, if yes, value: {1}", list.MoveNext(), list.Current);


        Console.ReadLine();
}

问题在于,当我在最后一个位置之后,列表会向我显示默认值,而yield创建的Iterator会一直显示最后一个值。

对于当前指数: 6
y-r:我下次要搬家吗? 错误,如果是,则值: 5
y-v:我下次应该搬家吗? 错误,如果是,则值: 5
我:我下次要搬家吗? 错误,如果是,则值: 0

为什么?

3 个答案:

答案 0 :(得分:7)

根据IEnumerator<T>.Current属性的MSDN文档:

  

Current未定义[when]:对MoveNext的最后一次调用返回false,表示集合的结束。

这意味着一旦Current返回MoveNext,枚举器的底层实现就可以从false返回任意值。它可以是0,6,-1,2147483647,或者只是随机选取的值;无论如何,你都不应该使用它。

答案 1 :(得分:2)

继承IEnumerator实现。 对于列表,有枚举器的实现描述:

[Serializable]
    public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator
    {
      private List<T> list;
      private int index;
      private int version;
      private T current;

      public T Current
      {
        get
        {
          return this.current;
        }
      }

      ....

      internal Enumerator(List<T> list)
      {
        this.list = list;
        this.index = 0;
        this.version = list._version;
        this.current = default (T); //there are default of T
      }

      ....
    }

对于产量实现是不同的。它返回最后一个值(导致当前未改变电流)。

这种差异可能是由于这段代码是由不同的人在不同的时间写的。此行为未定义,而且,如果您正确使用IEnumerables,您不应该看到此行为(原因通常人们不明确使用Enumerator.Next()但使用foreach,linq或索引直接访问(对于数组)) 。

答案 2 :(得分:2)

让我们来看看List<T>类使用的枚举器中发生了什么(这是List<int>.Enumerator)。实际上msdn表示当前值未定义,但我们可以分析Framework 4.0的实现。因此,当枚举器定位在最后一个元素MoveNextRare之后将被调用:

public bool MoveNext()
{
    List<T> ts = this.list;
    if (this.version != ts._version || this.index >= ts._size)
    {
        return this.MoveNextRare();
    }
    else
    {
        this.current = ts._items[this.index];
        List<T>.Enumerator<T> enumerator = this;
        enumerator.index = enumerator.index + 1;
        return true;
    }
}

因此列表有效(未更改)此方法返回defaut(T)值(对于int为0

private bool MoveNextRare()
{
    if (this.version != this.list._version)
        trow new InvalidOperationException();

    this.index = this.list._size + 1;
    this.current = default(T);
    return false;
}

生成的枚举器怎么样? C#生成枚举器类,它将为每个yield return语句提供状态。移至下一个状态会为此枚举器设置Current值:

bool MoveNext()
{
    bool flag;
    int state = this.state;
    if (state == 0)
    {
        this.state = -1;
        this.current = 0;
        this.state = 1;
        flag = true;
    }
    else if (state == 1)
    {
        this.state = -1;
        this.current = 1;
        this.state = 2;
        flag = true;
    }
    // ...
    else if (state == 5)
    {
        this.state = -1;
        this.current = 5;
        this.state = 6;
        flag = true;
    }
    else if (state == 6)
    {
        this.state = -1;
        flag = false;
        return flag;
    }
    else
    {
        flag = false;
        return flag;
    }
    return flag;
    flag = false;
    return flag;
}

有趣的是,this.current在最后一次分配后(状态为5时)未更改。这就是为什么对Current的所有后续调用都会返回由最后yield return次调用设置的值。