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.

  • On a related note, why does the C# compiler emit the transitive closure of all implemented interfaces on the type?

    The desktop CLR doesn't require this (and neither does the CLI spec, AFAIK), but the Compact Framework does require it (at least I ran into some problems years ago, when my compiler didn't do this).

  • Kudos on an excellent post.  I personally find it very difficult to write about a deep topic in such a way as to guide others, piece by piece, to the insights I've earned, but that's exactly what you've done here.

  • @Jeroen: Why not? If some frameworks require it, why not emit it for all of them? It's more work not to emit it, isn't it?

  • This post reminds me how much I wish all the now-confusing and useless classes like non-generic List could be removed, along with some obsolete features.

    I guess it must be really difficult to decide to abandon compatibility and simplify a programming language, since no language I know ever did after the very beginning. It could be well worth the cost, once every 5 or 10 years.

    The Silverlight version of the BCL does not have the non-generic collection types. That's usually how these things get eliminated; some "green field" new implementation without backwards compat burdens can get rid of the deadwood accumulated by the previous "legacy" version. -- Eric

  • I wonder why old collections such as MatchCollection have not been upgraded to implement the new interfaces in addition to to old ones. This makes using them with linq unpleasant (necessitating .Cast<Match>()).

  • Actually, implementing IList interface doesn't require you to be able to store any object in your collection. Storing object of wrong type (or even wrong value) is allowed to throw NotSupportedException. (Though, that's not as good as compilation error of course.) So there's nothing wrong in implementing IList for any ordered collection with indexed access. And List<T> implementing IList allows us to use it anywhere IList is required.

    However deriving IList<T> from IList would require every class implementing IList<T> to also implement all IList members and having to do that for collections that are never passed in place of IList would be a real pain. Developers don't like doing useless work.

  • I agree with Olivier, these confusing and rarely useful legacy interoperation features should be deprecated and removed.

    Particularly the non-generics components and that array covariance inconsistency.

  • ArrayList may be a "legacy" class, but if StackOverflow is any indication plenty of people still use it. I estimate that roughly 1% of C# questions on SO involve ArrayList or HashTable. And that's just "new" code -- you can imagine that there are plenty of large codebases using those classes that have no need to refactor.

  • I sometimes find myself dealing with reflection and System.Object collections, and I can't figure out if I really want to go through the trouble of List<object>, when logically it is no more useful than ArrayList.

    I have nothing to say about transitive closures of interfaces.

  • I am doing a Shameless Change of Subject, Skip this if you don't appreciate that.

    The interface is probably the language construct that helps most in structuring large software systems that are constantly under the forces of change. They can make software structured but flexible, and much more testable.

    At first glance interfaces are simple. An interface is a promise, i tell my new programmers when i train them, no more than a promise that a class is going to implement something. Simple as that.

    At the same time, interfaces are almost never fully understood. And interface inheritance is the culprit.

    An interface should consist of a minimal group of methods (op properties) that are needed to implement some responsibility. Since it is minimal, every user of that interface will use all the methods. This is important. Usercode should not depend on interface members they do not use.

    And interface inheritance will violate exactly that: You build a bigger interface, and soon you find that no usercode exists that uses all the members.

    To see why this is bad, you should give it a go: Take one of your classes and implement IDictionary<string, int> on it.

    You will find youself writing implementations for methods that you know you are never going to call. The nongeneric IEnumerable, for one.

    If this happens in .Net code, be sure that it is much worse in the jungle overhere.

    So why does this happen? First of all, interfaces should be written from the using code point of view. The callsite, i believe is the word. And sometimes  you want a variable for an object that can do IFoo and IBar. IRead and IWrite. ILoad and ISave. Makes perfect sense.

    Interface inheritance gives you an opportunity to combine IFoo and IBar into a new Interface IFooBar. It is all you have. So Interface Inheritance is you hammer, and therefor your problem must be a nail. Wham. Problem crushed. Not solved, chrushed.

    What was needed, was a variable that was IFoo and IBar at the same time. Interface Inheritance created a new interface that implements IFoo and IBar. That is not the same. As a result, for every usefull combination of interfaces, you get a new bigger interface. And for that to be usefull, all candidate implementing classes must now implement the bigger interface, even when that does not make sense. We'll someday end up having to implement IEverything on every class.

    Now if we only had a construct where you could say thay you had an IFooBar that is the combination of IFoo and IBar, and any instance of a class implementing IFoo and IBar (but not IFooBar) could be assigned to an IFooBar variable.....

  • > The Silverlight version of the BCL does not have the non-generic collection types.

    Eric, when I look at the mscorlib from Silverlight (v. 2.0.5.0), there's still an ArrayList present and List(T) also still implements IList. What did I miss?

  • This might be a little bit of off-topic, but as you are on the collection interfaces, the following point may also be interesting.

    We frequently discuss with colleagues the way the ReadOnlyCollection<T> is implemented in .NET Framework and how to work around it. As it implements ICollection<T>, it provides all the modification methods, such as Add and Remove, although they fail at runtime. Of course, there is the IsReadOnly property that is supposed to be called before doing a modification on an instance of ICollection<T>; however, it may be argued that it would actually be better to provide yet another interface between IEnumerable<T> and ICollection<T>, perhaps an ICollectionReader, which would have Count and IsReadOnly properties, but would not have all the collection-modification methods. Another interface, e.g. IListReader, would add an indexer to the mix.

    Furthermore, there is a discussion on the Internet about the non-nullable types (codebetter.com/.../i-want-non-nullable-types-in-c-4 ). It would also be extremely useful to have readonly non-nullable arrays, for example:

    public void A(readonly string![] messages) {

         messages[0] = "2-1-2"; // compile-time error, the array is marked as readonly

         if (messages[0] == null) {

                  .... // compile-time warning -- there will never be null as the type is set as string!

         }

    }

    public readonly string![] GetNames() {

        // the callers of this method will be sure that it never returns null and all objects inside of the collection are also

        // checked at compile-time to be different from null.

    }

    These are just thoughts, as there is no doubt that there are myriads of special cases that would pose significant problems if changes like these were to be introduced in the language. However, the rationale behind these thoughts is simple - in the enterprise solutions there is a lot of database querying methods (a data access layer that abstracts the system from an ORM), which shall usually return and accept only readonly collections. IEnumerable is not enough as it is unclear at the runtime -- whether the IEnumerable represents a fetched collection, or a deferred execution plan.

  • Patrick, ArrayList and Hashtable are actually internal to mscorlib in Silverlight (probably to avoid rewriting a lot of existing code). It's an implementation detail, so I'm totally fine with it. IList is still here though (but non-generic List is not).

    The reduced framework provides a nice excuse for breaking compatibility, which is good. But what could justify doing the same one day for full C#/.Net ?

  • I'd be surprised if the modern ArrayList isn't just a subclass or wrapper of List<object>. No need to separately implement two classes that do almost the same thing.

  • I think what the CLR actually lacks is "final" interface implementations. A way to specify that reimplementing an interface is forbidden to inheritors. It would certainly reduce the number of security critical classes that need to be sealed.

Page 1 of 2 (29 items) 12