关于c#:从IEnumerable转换为IEnumerator

Casting from IEnumerable to IEnumerator

我在玩IEnumerable/IEnumerable/。在我的一次试验中,我试图通过使用casting将IEnumerable类型的返回值分配给IEnumerator的值,然后尝试执行MoveNext()Current。虽然演员阵容没有错误,但我没有输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Animal
{
    public string AnimalType { get; set; }
    public Animal(string animal_type)
    {
        AnimalType = animal_type;
    }
}

class FarmCollection
{
    readonly Animal[] _farm =
          { new Animal("duck"), new Animal("cow"), new Animal("sheep") };

    public IEnumerable<Animal> GetEnumerable()
    {
        foreach (Animal a in _farm)
            yield return a;
    }
}

class Test
{
    public static void Main()
    {
        FarmCollection farm = new FarCollection();
        IEnumerator<Animal> rator = (IEnumerator<Animal>)farm.GetEnumerable();
        while (rator.MoveNext())
        {
            Animal a = (Animal)rator.Current;
            Console.WriteLine(a.AnimalType);
        }
    }
}

第一个问题:为什么我没有输出,而main只是返回?第二个问题:为什么从IEnumerableIEnumerator的铸件没有发出编译错误?


下面是您的FarmCollection.GetEnumerable方法在反编译时的样子:

1
2
3
4
5
6
7
public IEnumerable<Animal> GetEnumerable()
{
    FarmCollection.<GetEnumerable>d__0 <GetEnumerable>d__ =
        new FarmCollection.<GetEnumerable>d__0(-2);
    <GetEnumerable>d__.<>4__this = this;
    return <GetEnumerable>d__;
}

类型FarmCollection.d__0也由编译器生成。有关详细信息,请参阅本文。这是这个类的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
[CompilerGenerated]
private sealed class <GetEnumerable>d__0 : IEnumerable<Animal>, IEnumerable, IEnumerator<Animal>, IEnumerator, IDisposable
{
    private Animal <>2__current;

    private int <>1__state;

    private int <>l__initialThreadId;

    public FarmCollection <>4__this;

    public Animal 5__1;

    public Animal[] <>7__wrap3;

    public int <>7__wrap4;

    Animal IEnumerator<Animal>.Current
    {
        [DebuggerHidden]
        get
        {
            return this.<>2__current;
        }
    }

    object IEnumerator.Current
    {
        [DebuggerHidden]
        get
        {
            return this.<>2__current;
        }
    }

    [DebuggerHidden]
    IEnumerator<Animal> IEnumerable<Animal>.GetEnumerator()
    {
        FarmCollection.<GetEnumerable>d__0 <GetEnumerable>d__;
        if (Environment.CurrentManagedThreadId == this.<>l__initialThreadId && this.<>1__state == -2)
        {
            this.<>1__state = 0;
            <GetEnumerable>d__ = this;
        }
        else
        {
            <GetEnumerable>d__ = new FarmCollection.<GetEnumerable>d__0(0);
            <GetEnumerable>d__.<>4__this = this.<>4__this;
        }
        return <GetEnumerable>d__;
    }

    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.System.Collections.Generic.IEnumerable<ConsoleApplication479.Animal>.GetEnumerator();
    }

    bool IEnumerator.MoveNext()
    {
        bool result;
        try
        {
            switch (this.<>1__state)
            {
            case 0:
                this.<>1__state = -1;
                this.<>1__state = 1;
                this.<>7__wrap3 = this.<>4__this._farm;
                this.<>7__wrap4 = 0;
                goto IL_8D;
            case 2:
                this.<>1__state = 1;
                this.<>7__wrap4++;
                goto IL_8D;
            }
            goto IL_A9;
            IL_8D:
            if (this.<>7__wrap4 < this.<>7__wrap3.Length)
            {
                this.5__1 = this.<>7__wrap3[this.<>7__wrap4];
                this.<>2__current = this.5__1;
                this.<>1__state = 2;
                result = true;
                return result;
            }
            this.<>m__Finally2();
            IL_A9:
            result = false;
        }
        catch
        {
            this.System.IDisposable.Dispose();
            throw;
        }
        return result;
    }

    [DebuggerHidden]
    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    void IDisposable.Dispose()
    {
        switch (this.<>1__state)
        {
        case 1:
            break;
        case 2:
            break;
        default:
            return;
        }
        this.<>m__Finally2();
    }

    [DebuggerHidden]
    public <GetEnumerable>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
        this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
    }

    private void <>m__Finally2()
    {
        this.<>1__state = -1;
    }
}

所以在代码中,rator变量引用了这种类型的对象。由于这种类型实现了IEnumerator,这就解释了为什么强制转换没有失败。

现在,到第二个问题。注意,编译器生成的GetEnumerable方法构造了FarmCollection.d__0实例,并将值-2提供给构造函数。它存储在<>1__state变量中。现在,看看MoveNext()方法。它在<>1__state变量上有一个switch语句。如果该变量的值不是0或2,那么该方法将保证返回false,这意味着不会从枚举中返回任何值。

注意这个类中的GetEnumerator()方法如何将状态更改为0并返回相同的实例(在某些情况下,它返回一个状态为0的类的新实例),这将使MoveNext方法工作。

因此,基本上,如果不执行GetEnumerator()MoveNext方法将无法工作。


如果查看去糖代码,可以看到使用yield return将创建一个实现IEnumerableIEnumerator的内部类。这就是为什么演员阵容是有效的。

重要的一行在getEnumerable()方法中:返回new FarmCollection.d__1(-2);

所以,初始状态是-2,"还没有人请求枚举器状态"。如果调用GetEnumerator(),它会将其状态设置为0,即"枚举开始状态"。但是,由于您没有调用GetEnumerator(),它的状态将保持为-2,因此,当MoveNext()检查状态时,它将看到-2并返回false。