22

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.

Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • 2
    See http://stackoverflow.com/questions/4482557/what-interfaces-do-all-arrays-implement-in-c/4482567#4482567 – BoltClock Feb 24 '11 at 21:49

2 Answers2

41

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Compiled by Eric without looking at the specifications. – Metro Smurf Feb 24 '11 at 22:14
  • 2
    @Metro: If you look at the edit history you'll see that when I did look at the specification, I first thought it was wrong, then I realized that I was wrong and the specification was right. The moral of the story: *look at the spec first*. – Eric Lippert Feb 24 '11 at 22:18
  • 2
    @Eric - actually, the comment was an ode to your ability to provide an accurate answer without even looking at the specs. Was just a coincidence that you actually *had* to look at the spec this time ;) In looking at my comment and what you actually did, I can see that my comment may have come off as rude, which is 180 degrees from the intent. – Metro Smurf Feb 24 '11 at 22:24
  • 2
    @Metro: Indeed, how ironic that this is an example of a time when I messed it up. Twice! (And I did not think of the rude interpretation.) – Eric Lippert Feb 24 '11 at 22:28
  • @Eric: Thanks. Just read it but a few things I am still confused about. How does all these object[], int[], string[], SomeType[] is generated on the fly? This is not generics, right? It seems like this class object [] : System.Array, etc is something that can't be implemented by a user, right? – Joan Venge Feb 24 '11 at 23:27
  • @Eric: Also another thing I forgot to ask is, which one do you think is better? Casting the GetEnumerator() to IEnumerable, or just use foreach and yield? 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, etc? – Joan Venge Feb 24 '11 at 23:33
  • @Eric: Sorry for spamming, but also when you said implicit implementation, you mean in the form of Interface.Method, right? And if that's the case, what's the reason for implementing IEnumerable like that, instead of explicit implementation? 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? – Joan Venge Feb 24 '11 at 23:41
  • @Eric: Ok this is the last, but I would have thought the Array type would be something like this: public class Array : IEnumerable, etc. But instead there is some magical code about it then, right? – Joan Venge Feb 24 '11 at 23:44
  • @Eric: Thanks Eric for answering all my questions, appreciate it. I always thought the reason Array's being special was because of performance considerations, not because of the lack of generics. So you are saying, if generics was available in CLR v1, Arrays would be just like any other type like say List, etc and someone could easily create their own version of Arrays? – Joan Venge Feb 25 '11 at 00:19
  • 1
    @Joan: I think its likely that there would be some special array type because you want to guarantee that array elements are variables that are contiguous in memory, so that means the GC has special knowledge of them. But to the user, I imagine they'd appear to be just generic types, just as int? is just the generic type Nullable, so int[] would just be Array. – Eric Lippert Feb 25 '11 at 01:05
  • @Eric: Thanks Eric. That makes a lot of sense. – Joan Venge Feb 25 '11 at 02:06
2

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();
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005