13

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 a collection type if it implements the System.Collections.IEnumerable interface or implements the collection 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 ?

1
  • 1
    Now we know the question is primarily about the System.IEnumerable typo, I will also mention that this language specification should have allowed your sample without the cast to work by falling back to using IEnumerable interface. 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). Commented Jul 10, 2015 at 15:25

2 Answers 2

9

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:

  • A public, readable Current property
  • A public MoveNext() 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.

Sign up to request clarification or add additional context in comments.

18 Comments

@RoyiNamir: Ah, true - that's just another typo then. As you say, the existing example wouldn't compile - put public in and it's fine. Will edit.
But it's already all working like that without public i.imgur.com/gxqaEeq.png
@JamesThorpe: Nope - if you use explicit interface implementation, it isn't public in the normal way.
@Fabjan: No, that's not true. Explicit interface implementation messes with stuff, basically.
@RoyiNamir: With all the snippets going on (and your example which is a screenshot rather than something I can cut and paste) it's not clear where you're "having" to specify that, or what happens if you don't. If it's failing to compile without that, it's because it's finding the GetEnumerator method without that actually satisfying the pattern. But it does implement IEnumerable - I'm not sure why you think it doesn't.
|
4

The reference to System.IEnumerable on the MSDN is nothing more than a typo in an old language specification, no such interface exists, I believe it should be referring to System.Collections.IEnumerable.

You should really read the language specification of the version of C# you are using, the C# 5.0 language specification is available here.

Some further information about why failing to cast this example results in an error rather than falling back to using System.Collections.IEnumerable:

In the specification for foreach (section 8.8.4) you will see the rules have slightly changed (some steps have been cut for brevity):

  • Perform member lookup on the type X with identifier GetEnumerator and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match.
  • Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described below. It is recommended that a warning be issued if overload resolution produces anything except an unambiguous public instance method or no applicable methods.
  • If the return type E of the GetEnumerator method is not a class, struct or interface type, an error is produced and no further steps are taken.
  • Member lookup is performed on E with the identifier Current and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken.
  • The collection type is X, the enumerator type is E, and the element type is the type of the Current property.

So from the first bullet point we would find the public MyEnumerator GetEnumerator(), the second and third bullets go through without error. When we get to the fourth bullet point, no public member called Current is available which results in the error you are seeing without the cast, this exact situation never gives the compiler the opportunity to look for the enumerable interface.

When you explicitly cast your instance to an IEnumerable all of the requirements are satisfied as the type IEnumerable and the associated IEnumerator satisfy all of the requirements.

Also from the documentation:

The above steps, if successful, unambiguously produce a collection type C, enumerator type E and element type T. A foreach statement of the form

foreach (V v in x) embedded-statement 

is then expanded to:

{ E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } } 

So given your explicit cast to IEnumerable, you will end up with the following:

  • C = System.Collections.IEnumerable
  • x = new MyIntegers() as System.Collections.IEnumerable
  • E = System.Collections.IEnumerator
  • T = System.Object
  • V = System.Object
{ System.Collections.IEnumerator e = ((System.Collections.IEnumerable)(new MyIntegers() as System.Collections.IEnumerable)).GetEnumerator(); try { while (e.MoveNext()) { System.Object element = (System.Object)(System.Object)e.Current; Console.WriteLine(element); } } finally { System.IDisposable d = e as System.IDisposable; if (d != null) d.Dispose(); } } 

Which explains why using the cast works.

9 Comments

What error ? the code compiles as it is without public Current i.imgur.com/gxqaEeq.png
When you cast it to IEnumerable it compiles, but that's because you are explicitly using the type IEnumerable which satisfies all of the above (IEnumerable.GetEnumerator returns an IEnumerator which has public MoveNext() and Current members).
Ok let me understand please. Are you saying that the compiler translates it to the second one here — i.imgur.com/IfmLD5B.png
But my code implements System.Collection.IEnumerable . Not System.IEnumerable. (look at the conditions in my questions - where the _Otherwise_is in bold
@RoyiNamir It doesn't surprise me, I didn't read close enough to notice System.IEnumerable instead of System.Collections.IEnumerable.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.