Why does (does it really?) List<T> implement all these interfaces, not just IList<T>?
Asked Answered
L

5

67

List declaration from MSDN:

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

Reflector gives similar picture. Does List really implement all of these (if yes why)? I have checked:

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

    class A : I3 {}
    class B : I3, I2, I1 {}

    static void Main(string[] args)
    {
        var a = new A();
        var a1 = (I1)a;
        var a2 = (I2)a;
        var a3 = (I3)a;

        var b = new B();
        var b1 = (I1) b;
        var b2 = (I2)b;
        var b3 = (I3)b;
    }

it compiles.

[UPDATED]:

Guys, as i understand, all the replies stay that it:

class Program
{

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

    class A : I3 {}
    class B : I3, I2, I1 {}

    static void I1M(I1 i1) {}
    static void I2M(I2 i2) {}
    static void I3M(I3 i3) {}

    static void Main(string[] args)
    {
        var a = new A();
        I1M(a);
        I2M(a);
        I3M(a);

        var b = new B();
        I1M(b);
        I2M(b);
        I3M(b);

        Console.ReadLine();
    }
}

would give error, but it compiles and runs without any errors. Why?

Lepp answered 27/1, 2011 at 14:11 Comment(8)
Yes List does implement all those interfaces, why - because they are all relevant to a list collection. What is your code for? Is that a different question?Elinoreeliot
Your sample code has nothing to do with the List<T> class...Ruwenzori
I think he's asking why public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, etc. rather than just public class List<T> : IList<T>. Note that ICollection and IList (the non-generic ones) are not inherited from IList<T>.Aggregation
@James_Gaunt Yes, but IList<T> itself implements ICollection<T> and IEnumerable<T> so it seems redundant that List<T> explicit states it implements the others.Conn
@Ruwenzori - the sample code demonstrates interface implementation can be inherited. IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable, so why repeat them again on List<T>?Relict
Yes, I asked about the redundancy in declaration and the code i to prove that we can cast List to ICollection and IEnumerable even if they were not stated in declaration. Sorry for misunderstanding.Lepp
@Lepp - Tip - on Visual Studio, place the text cursor on a List<Something> (on the word List) and press F12, you will see its exact definition right there. You can keep pressing F12 on interfaces, to see their definitions.Relict
Re: "all the replies say that it would give an error" -- actually, none of the replies say that. Why do you suppose that they do?Scudo
S
129

UPDATE: This question was the basis of my blog entry for Monday April 4th 2011. Thanks for the great question.

Let me break it down into many smaller questions.

Does List<T> really implement all those interfaces?

Yes.

Why?

Because when an interface (say, IList<T>) inherits from an interface (say 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 is required to implement all the methods of all the interfaces in the transitive closure of its base interfaces?

Exactly.

Is a class 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 re-state the interfaces in D's base class list then the C# compiler will do an interface re-implementation. 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.

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 does Reflector show the whole list?

Reflector only has metadata to work from. Since putting in the full list is optional, Reflector 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, Reflector is attempting to help you by showing you more information rather than hiding information you might need.

BONUS QUESTION: Why does IEnumerable<T> inherit from IEnumerable but IList<T> does not inherit from IList?

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 IList<T> is not required to fulfill the whole contract of IList, so it does not inherit from it.

Scudo answered 27/1, 2011 at 15:52 Comment(1)
upvoted. The bonus question is asked this year: [Why generic IList<> does not inherit non-generic IList]Dictionary
O
5

The non-generic interfaces are for backward compatibility. If you write code using generics and want to pass your list to some module written in .NET 1.0 (which doesn't have generics) you still want this to succeed. Hence IList, ICollection, IEnumerable.

Orvalorvan answered 27/1, 2011 at 14:15 Comment(0)
A
5

Great question and great answer from Eric Lippert. This question came to my mind in the past, and the following is my understanding, hope it helps you.

Suppose I'm a C# programmer in another planet in the universe, in this planet, all animals can fly. So my program looks like:

interface IFlyable 
{
   void Fly();
}
interface IAnimal : IFlyable
{
   //something
}
interface IBird : IAnimal, IFlyable
{
}

Well you may be confused, since Birds are Animals, and all Animals can fly, why do we need to specify IFlyable in the interface IBird? OK Let's change it to:

interface IBird : IAnimal
{
}

I'm 100% sure that the program works like before, so nothing is changed? The IFlyable in the interface IBird is totally useless? Let's go on.

One day, my company sold the software to another company on the Earth. But on the Earth, not all animals can fly! So of course, we need to modify the interface IAnimal and the classes that implement it. After the modification, I found that IBird is incorrect now because birds in earth can fly! Now I regret removing IFlyable from IBird!

Alodium answered 6/4, 2011 at 9:9 Comment(2)
Except that Eric's answer says the source code actually only has the equivalent of IBird : IAnimal, and that the IFlyable only appears in MSDN, ILSpy, etc, as additional (inferred) information to help you.Intercalate
But then you have to go through all the other interfaces IDog, ICat, IRhinoceros, etc, which you presumably also added IFlyable to in your every-animal-flies universe, and remove IFlyable from them. Either way, you have a lot of updating to do.Machinist
U
3
  1. Yes they do, because List<T> can has the properties and method to fulfill all those interfaces. You don't know what interface someone is going to have declared as a parameter or return value, so the more interfaces list implements, the more versatile it can be.
  2. Your code will compile because upcasting (var a1 = (I1)a;) fails at runtime not compile time. I can do var a1 = (int)a and have it compile.
Undershirt answered 27/1, 2011 at 14:15 Comment(2)
it seems to be working... i added console.readline to the end and it got my input and terminated without any errorsLepp
It will run just fine. What makes you think it would not?Johnsten
A
2

List<> implements all of those interfaces in order to expose the functionality described by the different interfaces. It encompasses the features of a generic List, Collection and Enumerable (with backwards compatibility to the non-generic equivalents)

Appaloosa answered 27/1, 2011 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.