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
Cis said to be acollection typeif it implements theSystem.Collections.IEnumerableinterface or implements thecollection patternby 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 ?
System.IEnumerabletypo, I will also mention that this language specification should have allowed your sample without the cast to work by falling back to usingIEnumerableinterface. The specification never mentions raising an error when the collection pattern is partially implemented (which is what I addressed in my answer by highlighting the C# 5.0 specification does explain this error).