Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# `foreach` behaviour — Clarification?

Tags:

c#

foreach

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 ?

like image 781
Royi Namir Avatar asked Jul 10 '15 14:07

Royi Namir


People also ask

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

What is this C language?

C is a structured, procedural programming language that has been widely used both for operating systems and applications and that has had a wide following in the academic community. Many versions of UNIX-based operating systems are written in C.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

What is %d in C programming?

In C programming language, %d and %i are format specifiers as where %d specifies the type of variable as decimal and %i specifies the type as integer. In usage terms, there is no difference in printf() function output while printing a number using %d or %i but using scanf the difference occurs.


1 Answers

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.

like image 126
Jon Skeet Avatar answered Oct 03 '22 12:10

Jon Skeet