HelterSkelter HelterSkelter - 3 months ago 27
C# Question

Casting from IEnumerable to IEnumerator

I'm playing with

IEnumerable/<T>
and
IEnumerable/<T>
. In one of my trials, I tried to assign a returned value of type
IEnumerable<T>
to a value of
IEnumerator<T>
by using casting, and then trying to execute
MoveNext()
and
Current
. Though the casting issued no errors, I got no output:

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);
}
}
}


First question: Why I got no output, and Main simply returns?

Second question: Why did the casting from
IEnumerable<Animal>
to
IEnumerator<Animal>
didn't issue compiling error?

Answer

Here is how your FarmCollection.GetEnumerable method looks like when it is decompiled:

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

The type FarmCollection.<GetEnumerable>d__0 is also generated by the compiler. See this article for more details. Here is how this class looks like:

[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;
    }
}

So in your code, the rator variable refers to an object of this type. Since this type implements IEnumerator<Animal>, this explains why the cast did not fail.

Now, to your second question. Notice that the GetEnumerable method generated by the compiler constructs the FarmCollection.<GetEnumerable>d__0 instance and gives the value -2 to the constructor. This is stored in the <>1__state variable. Now, take a look at the MoveNext() method. It has a switch statement over the <>1__state variable. If the value of such variable is not 0 or 2, then the method is guaranteed to return false which means that no values would be returned from the enumeration.

Notice how the GetEnumerator() method in this class changes the state to 0 and returns the same instance (in some case, it returns a new instance of the class with state 0) which would make the MoveNext method work.

So basically the MoveNext method will not work without executing GetEnumerator().