Share via


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

  • Anonymous
    January 18, 2005
    What about IEnumerator<T> and ICollection<T>? AFAICS the non-generic versions of these interfaces are co-variant, so inheritance should be possible.
  • Anonymous
    January 18, 2005
    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?
  • Anonymous
    January 19, 2005
    The comment has been removed
  • Anonymous
    January 19, 2005
    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
  • Anonymous
    January 19, 2005
    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?
  • Anonymous
    January 21, 2005
    The comment has been removed
  • Anonymous
    January 26, 2005
    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.
  • Anonymous
    January 21, 2009
    PingBack from http://www.hilpers.it/2654004-implementare-una-interface-generica-perche