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.