Why does IEnumerable<T> inherits from IEnumerable


In the same vein as Krzysztof’s post on the reason why IEnumerator extends IDisposable, I thought I’d post this thoughtful response from Anders Hejlsberg on why IEnumerable<T> inherits from IEnumerable.

 

Ideally all of the generic collection interfaces (e.g. ICollection<T>, IList<T>) would inherit from their non-generic counterparts such that generic interface instances could be used both with generic and non-generic code. For example, it would be convenient if an IList<T> could be passed to code that expects an IList.

 

As it turns out, the only generic interface for which this is possible is IEnumerable<T>, because only IEnumerable<T> is contra-variant: In IEnumerable<T>, the type parameter T is used only in “output” positions (return values) and not in “input” positions (parameters). ICollection<T> and IList<T> use T in both input and output positions, and those interfaces are therefore invariant. (As an aside, they would have been contra-variant if T was used only in input positions, but that doesn’t really matter here.)

 

The effect of having a non-contra-variant generic interface inherit from its non-generic counterpart is best illustrated with an example. Imagine IList<T> inherited from IList. IList<T> would now have two Add methods–its own Add(T) method and an inherited Add(object) method. While this is technically possible, the inherited Add(object) method would completely defeat the purpose of IList<T> because it can be called with an object of any type. In other words, I would be able to write:

 

IList<int> numbers = GetIntList(…);

numbers.Add(“hello”);

 

and the call to Add would invoke IList<int>’s implementation of Add(object), which presumably would throw an exception at run-time. The strong typing provided by IList<T> would be lost and there would be little reason to have IList<T> in the first place. For this reason, IList<T> doesn’t inherit from IList. Of course, we recommend that collections implement both, but now the loss of type safety is called out because I have to explicitly obtain the IList implementation:

 

IList<int> numbers = GetIntList(…);

IList objects = (IList)numbers;

objects.Add(“hello”);

 

The story is different for IEnumerable<T>. It has one method, GetEnumerator(), which returns an IEnumerator<T>, which in turn has a Current property of type T. In all cases, T occurs in an output position, and strong typing is not defeated if methods returning object are added. Intuitively, it is “safe” to treat a T as an object, but not vice versa.

 

So, to answer your question, IEnumerable<T> inherits from IEnumerable because it can! 🙂

 

Comments (8)

  1. Fabian says:

    What about IEnumerator<T> and ICollection<T>? AFAICS the non-generic versions of these interfaces are co-variant, so inheritance should be possible.

  2. ICollection<T> is not co-variant for example has:

    void Add(T item);

    So for ICollection<int> you would have:

    void Add(int item); //from ICollection<T>

    void Add(object item); //from ICollection

    So you run into the problem Anders mentions where you lost the strong-typing of ICollection<T>.

    Does that help?

  3. Grim says:

    It would be nice if there were a mechanism to specify that my new class should implement IList<T>, as well as auto-implementing EXPLICIT interface members for the parent interface (IList).

    So, I would have:

    public void int Add(T item);

    void int IList.Add(object item);

    Where the IList interface would still be present, but if I wanted to be boneheaded and use it other than intended, I would have to knowingly (one would assume) cast the object to an IList before being able to add a non-T object: ((IList)myObject).Add(nonT);

    If you wanted to make sure that would fail, you could have the compiler auto-generate the explicit IList interfaces do a cast and redirect through the public interfaces:

    public class MyList : IList<T>

    {

    public void Add ( T item )

    {

    // Add to innerList

    }

    void IList.Add ( object item )

    {

    this.Add ( (T)item );

    }

    }

    Which would cause an InvalidCastException if the item weren’t a valid T.

    I realize at first glance it seems that this would cause unnecessary boxing if T is a value type, but if the developer does the explicit cast from T to object, then the performance hit is completely on their own head.

    The developer could, of course, override this functionality by explicity extending IList in their code, and implementing their own explicit (or not) versions of the non-typed interface.

    public class MyList : IList<T>, IList

    {

    public void Add ( T item )

    {

    // Add to innerList

    }

    public void Add ( object item )

    {

    if ( item is T )

    this.Add( (T)item );

    else

    // Do something other than throw an InvalidCastException

    }

    }

  4. Brad Abrams has pointed to this post here. Kryzystofs bit here makes perfect sense to me and honest it’s not obscure! (well at least not around here) Perhaps it seems obscure to the folks writing stuff like the CLR, but to those of us doing LOB apps out h

  5. Fabian says:

    Brad,

    ICollection (non-<T>) does not seem to have an Add method (at least not in 1.1, and the current docs on msdn2 still don’t show one), so there wouldn’t be a problem with an Add(object) method. Am I missing something?

  6. John Melville says:

    Just a crazy idea; why not turn it arround and have IList inherit from IList<T> i.e.

    Interface IList<t> { … }

    Interface IList: IList<object> {/*Note NOTHING goes here*/}

    All code going foreward could use IList<T> and could access any collection, regardless of whether it is strongly typed or not.

    Furthermore, I would argue that for much of the code that currently uses IList, switching to IList<T> would be a nonbreaking change. (Some of the code would have to be rewritten so other types interacting with the collection would be strongly typed through generics. This transition does not have to be in the current version because whenever you do it, its not a breaking change.)

    I am sure an idea this obvious has been considered and rejected, I’m just curious about why it breaks down.

  7. Kirk Ferdmann says:

    IMHO for a class that implements a generic interface (like IList<T>) it would be useful to have an "adaptor" class that implements non-generic interface and forwards the calls to the real one. That way IList<T> can be converted into IList only when needed.