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

前端 未结 2 758
借酒劲吻你
借酒劲吻你 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 GetEnumerator ( )
    {
         return ((IEnumerable)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, IEnumerable and so on.
    • therefore every array type T[] is convertible to IEnumerable

    Notice that the third point was NOT

    • every array type T[] implements IList, IEnumerable 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 explicitly. It is convertible to IEnumerable, 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, IEnumerable
    {
        IEnumerator IEnumerable.GetEnumerator() { ... }
        int IList.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, or just use foreach and yield?

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

    I would probably cast Values to IEnumerable 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 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 : IEnumerable 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.

    提交回复
    热议问题