I've read Eric's article here about foreach enumeration and about the different scenarios where foreach can work
In order to prevent the old C# version to do boxing , the C# team enabled duck typing for foreach to run on a non- Ienumerable collection.(A public GetEnumerator
that return something that has public MoveNext
and Current
property is sufficient(.
So , Eric wrote a sample :
class MyIntegers : IEnumerable
{
public class MyEnumerator : IEnumerator
{
private int index = 0;
object IEnumerator.Current { return this.Current; }
int Current { return index * index; }
public bool MoveNext()
{
if (index > 10) return false;
++index;
return true;
}
}
public MyEnumerator GetEnumerator() { return new MyEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}
But I believe it has some typos (missing get accessor at Current
property implementation) which prevent it from compiling (I've already Emailed him).
Anyway here is a working version :
class MyIntegers : IEnumerable
{
public class MyEnumerator : IEnumerator
{
private int index = 0;
public void Reset()
{
throw new NotImplementedException();
}
object IEnumerator.Current {
get { return this.Current; }
}
int Current {
get { return index*index; }
}
public bool MoveNext()
{
if (index > 10) return false;
++index;
return true;
}
}
public MyEnumerator GetEnumerator() { return new MyEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}
Ok.
According to MSDN :
A type
C
is said to be acollection type
if it implements theSystem.Collections.IEnumerable
interface or implements thecollection pattern
by meeting all of the following criteria:
C contains a public instance method with the signature GetEnumerator() that returns a struct-type, class-type, or interface-type, which is called E in the following text.
E contains a public instance method with the signature MoveNext() and the return type bool.
E contains a public instance property named Current that permits reading the current value. The type of this property is said to be the element type of the collection type.
OK. Let's match the docs to Eric's sample
Eric's sample is said to be a collection type
because it does implements the System.Collections.IEnumerable
interface ( explicitly though). But it is not(!) a collection pattern
because of bullet 3 : MyEnumerator
does not public instance property named Current.
MSDN says :
If the collection expression is of a type that implements the collection pattern (as defined above), the expansion of the foreach statement is:
E enumerator = (collection).GetEnumerator();
try {
while (enumerator.MoveNext()) {
ElementType element = (ElementType)enumerator.Current;
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
Otherwise , The collection expression is of a type that implements System.IEnumerable (!), and the expansion of the foreach statement is:
IEnumerator enumerator =
((System.Collections.IEnumerable)(collection)).GetEnumerator();
try {
while (enumerator.MoveNext()) {
ElementType element = (ElementType)enumerator.Current;
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
Question #1
It seems that Eric's sample neither implements the
collection pattern
nor System.IEnumerable
- so it's not supposed to match any of the condition specified above. So how come I can still iterate it via :
foreach (var element in (new MyIntegers() as IEnumerable ))
{
Console.WriteLine(element);
}
Question #2
Why do I have to mention new MyIntegers() as IEnumerable
? it's already Ienumerable (!!) and even after that , Isn't the compiler is already doing the job by itself via casting :
((System.Collections.IEnumerable)(collection)).GetEnumerator() ?
It is right here :
IEnumerator enumerator =
((System.Collections.IEnumerable)(collection)).GetEnumerator();
try {
while (enumerator.MoveNext()) {
...
So why it still wants me to mention as Ienumerable ?
MyEnumerator does not has the required public methods
Yes it does - or rather, it would if Current
were public. All that's required is that it has:
Current
propertyMoveNext()
method with no type arguments returning bool
The lack of public
here was just another typo, basically. As it is, the example doesn't do what it's meant to (prevent boxing). It's using the IEnumerable
implementation because you're using new MyIntegers() as IEnumerable
- so the expression type is IEnumerable
, and it just uses the interface throughout.
You claim that it doesn't implement IEnumerable
, (which is System.Collections.IEnumerable
, btw) but it does, using explicit interface implementation.
It's easiest to test this sort of thing without implementing IEnumerable
at all:
using System;
class BizarreCollection
{
public Enumerator GetEnumerator()
{
return new Enumerator();
}
public class Enumerator
{
private int index = 0;
public bool MoveNext()
{
if (index == 10)
{
return false;
}
index++;
return true;
}
public int Current { get { return index; } }
}
}
class Test
{
static void Main(string[] args)
{
foreach (var item in new BizarreCollection())
{
Console.WriteLine(item);
}
}
}
Now if you make Current
private, it won't compile.
See more on this question at Stackoverflow