So many interfaces!

So many interfaces!

Rate This
  • Comments 29

Today, another question from StackOverflow, and again, presented as a dialogue as is my wont.

The MSDN documentation for List<T> says that the class is declared as

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>,
                      
IList, ICollection, IEnumerable

Does List<T> really implement all those interfaces?

Yes.

Why so many interfaces?

Because when an interface like IList<T> inherits from an interface like IEnumerable<T> then implementers of the more derived interface are required to also implement the less derived interface. That's what interface inheritance means; if you fulfill the contract of the more derived type then you are required to also fulfill the contract of the less derived type.

So a class or struct is required to implement all the methods of all the interfaces in the transitive closure of its base interfaces?

Exactly.

Is a class (or struct) that implements a more-derived interface also required to state in its base type list that it is implementing all of those less-derived interfaces?

No.

Is the class required to not state it?

No.

So it's optional whether the less-derived implemented interfaces are stated in the base type list?

Yes.

Always?

Almost always:

interface I1 {}
interface I2 : I1 {}
interface I3 : I2 {}

It is optional whether I3 states that it inherits from I1.

class B : I3 {}

Implementers of I3 are required to implement I2 and I1, but they are not required to state explicitly that they are doing so. It's optional.

class D : B {}

Derived classes are not required to re-state that they implement an interface from their base class, but are permitted to do so. (This case is special; see below for more details.)

class C<T> where T : I3
{
  public virtual void M<U>() where U : I3 {}
}

Type arguments corresponding to T and U are required to implement I2 and I1, but it is optional for the constraints on T or U to state that.

It is always optional to re-state any base interface in a partial class:

partial class E : I3 {}
partial class E {}

The second half of E is permitted to state that it implements I3 or I2 or I1, but not required to do so.

OK, I get it; it's optional. Why would anyone unnecessarily state a base interface?

Perhaps because they believe that doing so makes the code easier to understand and more self-documenting.

Or, perhaps the developer wrote the code as

interface I1 {}
interface I2 {}
interface I3 : I1, I2 {}

and the realized, oh, wait a minute, I2 should inherit from I1. Why should making that edit then require the developer to go back and change the declaration of I3 to not contain explicit mention of I1? I see no reason to force developers to remove redundant information.

Aside from being easier to read and understand, is there any technical difference between stating an interface explicitly in the base type list and leaving it unstated but implied?

Usually no, but there can be a subtle difference in one case. Suppose you have a derived class D whose base class B has implemented some interfaces. D automatically implements those interfaces via B. If you restate the interfaces in D's base class list then the C# compiler will do an interface reimplementation. The details are a bit subtle; if you are interested in how this works then I recommend a careful reading of section 13.4.6 of the C# 4 specification. Basically the idea is that the compiler "starts over" and figures out which methods implement which interfaces.

UPDATE: See this follow-up article for details of interface re-implementation.

Does the List<T> source code actually state all those interfaces?

No. The actual source code says

public class List<T> : IList<T>, System.Collections.IList

Why does MSDN have the full interface list but the real source code does not?

Because MSDN is documentation; it's supposed to give you as much information as you might want. It is much more clear for the documentation to be complete all in one place than to make you search through ten different pages to find out what the full interface set is.

Why do tools like Reflector or the object browser show the whole list?

Those tools do not have the source code. They only have metadata to work from. Since putting in the full list is optional, the tool has no idea whether the original source code contains the full list or not. It is better to err on the side of more information. Again, the tool is attempting to help you by showing you more information rather than hiding information you might need.

I noticed that IEnumerable<T> inherits from IEnumerable but IList<T> does not inherit from IList. What's up with that?

The same reason why IEnumerable<T> can be made covariant in T but IList<T> cannot. A sequence of integers can be treated as a sequence of objects, by boxing every integer as it comes out of the sequence. But a read-write list of integers cannot be treated as a read-write list of objects, because you can put a string into a read-write list of objects. An IEnumerable<T> can easily fulfill the contract of IEnumerable just by adding a boxing helper method. But IList<T> is not required to fulfill the whole contract of IList, so it does not inherit from it.

Why then does List<T> implement IList?

It is a bit odd, since List<T> for any type other than object does not fulfill the full contract of IList. It's probably to make it easier on people who are updating old C# 1.0 code to use generics; those people were probably already ensuring that only the right types got into their lists. And most of the time when you're passing an IList around, it is so the callee can get by-index access to the list, not so that it can add new items of arbitrary type.

  • There are many cases where interface inheritance would make sense, if a "default implementation" facility existed for interface methods (as in 99% of cases the implementation would be exactly the same). IEnumerator/IEnumerator<T> is one such example.

  • @Ferdinand

    Such a system exists:

    void Frob<T>(T t)

       Where T : IFoo, IBar

    {

    }

    Hey presto there's you variable containing something known to implement both (known to the compiler that is)

    Since I got generics I haven't found too many occasions when this wasn't sufficient. More constraints on non interface related mechanisms like operators and the like, that would be nice though...

  • @Shuggy

    I'm not sure i understand what you are doing, an interface is quite the opposite of a Generic, after all.

    Lets try. I need a class with an IFoo/IBar field:

    public class C<T>  where T:IFoo, IBar

    {

          public T f;

    }

    I instantiate it...how?

       var myC = new C<what goes here?> ();

    and assign f

        if  (MyLuckyDay) {

             myC.f = new SomeClassWithFooAndBar();

        }

        else {

             myC.f = new TotallyDifferentClassWithFooAndBar();

        }

  • @Ferdinand at least with generic methods you can avoid the ILoad and ISave problem some because you can define your method to act on types that implement multiple interfaces without defining a new interface.

    For example you can declare the following

           public T MyFunction<T>(T input) where T : ILoad<T>, ISave<T>

           {

    Or maybe it is something more like

           public U MyFunction<T, U>(T input) where T : ILoad<U>, ISave<U>

           {

  • @Ferdinand Sorry I did not look at the second page first, but I do not think I quite understand the problem you are trying to solve.

  • @Bill P. Godfrey,

    ArrayList and List<T> are not related in any way, because neither one could inherit from the other. ArrayList existed prior to List<T>, and changing its parent class would be a breaking change; and implementing List<T> by deriving from ArrayList would require boxing all stored objects when T is a value type, which would counteract the benefit of storing a list of value types using generics.

  • "...most of the time when you're passing an IList around, it is so the callee can get by-index access to the list, not so that it can add new items of arbitrary type."

    Which is precisely why IList should have been broken up into read-only and write-only interfaces, which IList could implement. Similarly for ICollection and IDictionary, and their generic equivalents. Why this was not done for the generic versions when the problem with the non-generic versions was already well known has always puzzled me.

  • Slightly off-topic, but IList<> is one of my saddest points with C#. Ever since "Immutability in C" I've been striving for immutability where I can, but IList throws a real spanner in the works.

    Why-oh-why does IList not inherit from an "IReadOnlyList"? Why has this never been patched in between C# revisions? So many of my classes have to throw "Read only list" exception, when all I really wanted was an IEnumerable with indexing access/count.

    Having an IReadOnlyList just adds *so* much information to a function - you can see at a glance that you can't modify what it returns, or conversely - that *it won't modify what you pass it*. Wrapping an IList in a ReadOnlyCollection is not the same - it just doesn't cut it.

    I suppose it could be opening the way for an IReadOnlyDictionary, etc - perhaps something Eric and Co are trying to avoid - but come on, the advantages of an IReadOnlyList would be more than worth it. Why am I forced to implement just half the interface?

    -sad face-

  • Not to mention that IReadOnlyList<> would be able to be covariant - how useful would that be!

  • @Ferdinand.

    Interfaces and generics are orthogonal, you may use either to constrain the set of possible things which may reside in a variable (or value)

    I too don't understand what you're getting at because no matter what you use to constrain what can be in a variable the problem of what actually goes in it remains. The whole point of having interfaces is to allow you the freedom of what goes in it. (indeed the freedom for the implementer of a piece of code to never actually know)

  • Hi Mr Lippert:

    Would it be OK if I translate this article and post it on my blog?

    Of course I will provide the original post address and author.

    Regards,

    Cui

  • As a side question, why is it that most collection interfaces have not been split into "command" and "query" interfaces? For example IDictionary is a mix of both queries (read only) and commands (write and sometimes read). It makes readability of code a lot worse since you cannot specify that you only need the query part of the interface. It also means you have to use a ReadOnlyDictionary (or ReadOnlyCollection) who implement the entire interface incl the command part, which by definition are implemented as throwing e.g. NotImplementedException. It seems extremely redundant and not the least confusing.

  • For all people wanting and IReadOnlyList<> there is a similar  proposal on connect that could be voted for connect.microsoft.com/.../add-an-iindexable-t-accessible-by-index-interface (Even if the comment in the issue about Count being slow is false as long as the enumeration also implement ICollection)

  • I sympathise with the comments of David Nelson and Alex Davies re: IReadOnlyList<T>. And very glad to see from VirtualBlackFox's linked Connect issue that something like that may make it into the BCL soon.

    On the other hand, there would still be no requirement on implementations of the read-only interface to genuinely be immutable. Nor could such a requirement be meaningfully retrofitted to IList<T>, as it would break all existing code that sneakily mutates the object's fields when implementing getters and other read-only methods. Of course to do so can sometimes be very useful: a lazy file-backed list that reads its content from a file the first time you access it, and then caches the list in memory thereafter. It's logically readonly, but technically (internally) it mutates its contents once.

    This is one of the gotchas of const-correctness in C++. The const modifier can easily be "casted away" in the internals of a class, and so all the static checking may be rendered pointless if you're trying to rely on it to ensure immutability. We need the safety of guaranteed immutability, but we need the power to bend the rules to achieve performance. If C# 6 (or 7) can reconcile these needs without collapsing under its own complexity it will be quite an achievement.

Page 2 of 2 (29 items) 12