Приведение из 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> enumerator = (IEnumerator<Animal>)farm.GetEnumerable();
        while (enumerator.MoveNext())
        {
            Animal a = (Animal)enumerator.Current;
            Console.WriteLine(a.AnimalType);
        }
    }
}

Первый вопрос: почему у меня нет вывода, а Main просто возвращается?
Второй вопрос: почему преобразование из IEnumerable<Animal> в IEnumerator<Animal> не привело к ошибке компиляции?


person OfirD    schedule 08.09.2016    source источник
comment
Вы должны использовать farm.GetEnumerable().GetEnumerator() вместо приведения   -  person Matias Cicero    schedule 09.09.2016
comment
Приведение разрешено, потому что объект, возвращенный из GetEnumerable(), может реализовывать как IEnumerable<Animal>, так и IEnumerator<Animal>. Если вам нужен базовый перечислитель, вы должны вызвать для него GetEnumerator().   -  person Lee    schedule 09.09.2016
comment
Приведение разрешено, но изменение кода на farm.GetEnumerable().GetEnumerator(); устраняет проблему. Но я не знаю почему.   -  person    schedule 09.09.2016


Ответы (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;
    }
}

Итак, в вашем коде переменная enumerator относится к объекту этого типа. Поскольку этот тип реализует IEnumerator<Animal>, это объясняет, почему приведение не завершилось ошибкой.

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

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

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

person Yacoub Massad    schedule 08.09.2016
comment
Спасибо. Кстати, какой инструмент вы использовали для такого удобного отображения сгенерированного кода? - person OfirD; 09.09.2016
comment
Пожалуйста. Я использую ILSpy. Просто не забудьте снять флажок декомпилировать перечислители (доходность) в меню «Просмотр» - ›Параметры. - person Yacoub Massad; 09.09.2016
comment
Отлично, я посмотрю. - person OfirD; 09.09.2016

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

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

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

person Dennis_E    schedule 08.09.2016
comment
Спасибо. Я не был знаком с Try Roslyn, так что спасибо вам и за это. - person OfirD; 09.09.2016