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?
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()
.
If you look at the de-sugared code, you can see that using yield return
will create an inner class that implements both IEnumerable<T>
and IEnumerator<T>
. That's why the cast is valid.
The important line is in the method GetEnumerable():
It returns new FarmCollection.<GetEnumerable>d__1(-2);
So, the initial state is -2, the "No-one-has-requested-an-Enumerator-yet state". If you call GetEnumerator()
it will set its state to 0, the "start of enumeration state".
But since you are not calling GetEnumerator()
, its state will remain -2 and therefore, when MoveNext()
checks the state, it will see -2 and return false.
上一篇: 并行对象初始化