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

前端 未结 2 757
借酒劲吻你
借酒劲吻你 2020-12-15 05:49

I have some code like this:

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

    public IEnumerator

        
                      
相关标签:
2条回答
  • 2020-12-15 06:19

    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.

    0 讨论(0)
  • 2020-12-15 06:19

    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();
    }
    
    0 讨论(0)
提交回复
热议问题