Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I use the enumerator of an array, instead of implementing it myself?

I have some code like this:

public class EffectValues : IEnumerable<object>
{
    public object [ ] Values { get; set; }

    public IEnumerator<object> GetEnumerator ( )
    {
        return this.Values.GetEnumerator ( );
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ( )
    {
        return this.GetEnumerator ( );
    }
}

But the compiler complains saying:

"Cannot implicitly convert type 'System.Collections.IEnumerator' to 'System.Collections.Generic.IEnumerator'. An explicit conversion exists (are you missing a cast?)"

I thought the Array type implemented both IEnumerable interfaces, does it not? Because I can use Linq features on the Values instance directly.

like image 336
Joan Venge Avatar asked Feb 24 '11 21:02

Joan Venge


People also ask

How to list the elements of an array using GetEnumerator?

Array. Get Enumerator Method Returns an IEnumerator for the Array. An IEnumerator for the Array. The following code example shows how to use GetEnumerator to list the elements of an array. using System; public class SamplesArray { public static void Main() { // Creates and initializes a new Array.

How are enumerators implemented in Ruby?

Finally, we went through a couple of different ways in which enumerators can be implemented in Ruby, understanding each of them with an example. Enumerators are present everywhere throughout the Ruby language. Primitive classes such as Integer and String also utilize enumerators to carry out repetitive tasks easily.

What is enumerate () method in Python?

Enumerate() in Python. A lot of times when dealing with iterators, we also get a need to keep a count of iterations. Python eases the programmers’ task by providing a built-in function enumerate() for this task. Enumerate() method adds a counter to an iterable and returns it in a form of enumerate object.

Can enumerators be used to modify a collection?

Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection. Initially, the enumerator is positioned before the first element in the collection. Reset also brings the enumerator back to this position. At this position, Current is undefined.


Video Answer


2 Answers

This is a subtle and a bit unfortunate. The easy workaround is:

public IEnumerator<object> GetEnumerator ( )
{
     return ((IEnumerable<object>)this.Values).GetEnumerator ( );     
} 

I thought the Array type implemented both IEnumerable interfaces, does it not?

The rules are:

  • System.Array implements IEnumerable "implicitly", with public methods.
  • every array type T[] inherits from System.Array.
  • every array type T[] implements IList<T>, IEnumerable<T> and so on.
  • therefore every array type T[] is convertible to IEnumerable<T>

Notice that the third point was NOT

  • every array type T[] implements IList<T>, IEnumerable<T> and so on with public methods and properties defined on T[] that implicitly implement the members

And there you go. When you look up GetEnumerator, we look it up on object[] and don't find it, because object[] implements IEnumerable<object> explicitly. It is convertible to IEnumerable<object>, and convertibility doesn't count for lookups. (You wouldn't expect a method of "double" to appear on int just because int is convertible to double.) We then look at the base type, and find that System.Array implements IEnumerable with a public method, so we've found our GetEnumerator.

That is, think about it like this:

namespace System
{
    abstract class Array : IEnumerable
    {
        public IEnumerator GetEnumerator() { ... }
        ...
    }
}

class object[] : System.Array, IList<object>, IEnumerable<object>
{
    IEnumerator<object> IEnumerable<object>.GetEnumerator() { ... }
    int IList<object>.Count { get { ... } }
    ...
}

When you call GetEnumerator on object[], we don't see the implementation that is an explicit interface implementation, so we go to the base class, which does have one visible.

How do all the object[], int[], string[], SomeType[] classes get generated "on the fly"?

Magic!

This is not generics, right?

Right. Arrays are very special types and they are baked in at a deep level into the CLR type system. Though they are very similar to generics in a lot of ways.

It seems like this class object [] : System.Array is something that can't be implemented by a user, right?

Right, that was just to illustrate how to think about it.

Which one do you think is better: Casting the GetEnumerator() to IEnumerable<object>, or just use foreach and yield?

The question is ill-formed. You don't cast the GetEnumerator to IEnumerable<object>. You either cast the array to IEnumerable<object> or you cast the GetEnumerator to IEnumerator<object>.

I would probably cast Values to IEnumerable<object> and call GetEnumerator on it.

I will probably use casting but I am wondering if this is a place where you or some programmer who could read the code, would think it's less clear.

I think it's pretty clear with the cast.

when you said implicit implementation, you mean in the form of Interface.Method, right?

No, the opposite:

interface IFoo { void One(); void Two(); }
class C : IFoo
{
    public void One() {} // implicitly implements IFoo.One
    void IFoo.Two() {} // explicitly implements IFoo.Two
}

The first declaration silently implements the method. The second is explicit about what interface method it implements.

What's the reason for implementing IEnumerable<T> like that, instead of implicit implementation with public methods? I got curious because you said "This is a subtle and a bit unfortunate", so it seems like it's because of an older decision that forced you to do this I imagine?

I don't know who made this decision. It is kind of unfortunate though. It's confused at least one user -- you -- and it confused me for a few minutes there too!

I would have thought the Array type would be something like this: public class Array<T> : IEnumerable<T> etc. But instead there is some magical code about it then, right?

Right. As you noted in your question yesterday, things would have been a lot different if we'd had generics in CLR v1.

Arrays are essentially a generic collection type. Because they were created in a type system that did not have generics, there has to be lots of special code in the type system to handle them.

Next time you design a type system put generics in v1 and make sure you get strong collection types, nullable types and non-nullable types baked in to the framework from the beginning. Adding generics and nullable value types post hoc was difficult.

like image 103
Eric Lippert Avatar answered Oct 08 '22 03:10

Eric Lippert


You have to cast the array to IEnumerable<object> to be able to access the generic enumerator:

public IEnumerator<object> GetEnumerator() {
  return ((IEnumerable<object>)this.Values).GetEnumerator();
}
like image 39
Guffa Avatar answered Oct 08 '22 01:10

Guffa