Приведение из IEnumerable в IEnumerator

Я играю с IEnumerable/<T> а также IEnumerable/<T>, В одном из моих испытаний я попытался присвоить возвращаемое значение типа IEnumerable<T> до значения IEnumerator<T> с помощью приведения, а затем пытается выполнить MoveNext() а также Current, Хотя кастинг не выдал ошибок, я не получил вывод:

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 просто возвращается?
Второй вопрос: почему кастинг от IEnumerable<Animal> в IEnumerator<Animal> не выдает ошибку компиляции?

2 ответа

Решение

Вот как твоя FarmCollection.GetEnumerable Метод выглядит так, как будто он декомпилирован:

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.<GetEnumerable>d__0 также генерируется компилятором. Смотрите эту статью для более подробной информации. Вот как выглядит этот класс:

[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 <a>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.<a>5__1 = this.<>7__wrap3[this.<>7__wrap4];
                this.<>2__current = this.<a>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<Animal>, это объясняет, почему актерский состав не провалился.

Теперь к вашему второму вопросу. Обратите внимание, что GetEnumerable Метод, сгенерированный компилятором, создает FarmCollection.<GetEnumerable>d__0 экземпляр и дает значение -2 конструктору. Это хранится в <>1__state переменная. Теперь взгляните на MoveNext() метод. Он имеет оператор переключения над <>1__state переменная. Если значение такой переменной не равно 0 или 2, метод гарантированно вернет false Это означает, что никакие значения не будут возвращены из перечисления.

Обратите внимание, как GetEnumerator() метод в этом классе изменяет состояние на 0 и возвращает тот же экземпляр (в некоторых случаях он возвращает новый экземпляр класса с состоянием 0), что делает MoveNext метод работы.

Так что в основном MoveNext метод не будет работать без выполнения GetEnumerator(),

Если вы посмотрите на обезвоженный код, вы можете увидеть, что с помощью yield return создаст внутренний класс, который реализует оба IEnumerable<T> а также IEnumerator<T>, Вот почему актерский состав действителен.

Важная строка в методе GetEnumerable():
Возвращается new FarmCollection.<GetEnumerable>d__1(-2);

Итак, начальное состояние - -2, состояние "пока никто не запросил, а перечислитель все еще". Если вы позвоните GetEnumerator() он установит свое состояние на 0, "начало состояния перечисления".
Но так как ты не звонишь GetEnumerator(), его состояние останется -2 и, следовательно, когда MoveNext() проверяет состояние, оно увидит -2 и вернет false.

Другие вопросы по тегам