locked
Why shouldn't List<T> assume the inheritance chain of T

    Question

  • It seems to me that it would make sense for the following code snippet to work because clearly a List<B> is a List<A>. But the generics/compiler does not make this assumption. Unless there are significant reasons as to why this would not work, I think it would be a neat feature to have. Any possibility of getting it?

    Code Snippet

    class A {}

    class B : A {}


    static class Program {

    static void Main() {

    List<B> theBList = new List<B>();

    theBList.Add(new B());

    theBList.Add(new B());


    // error CS1503: Argument '1': cannot convert from

    // 'System.Collections.Generic.List<B>' to

    // 'System.Collections.Generic.List<A>'

    DoSomething(theBList);

    }


    static void DoSomething(List<A> theAList) {

    // Doing something to list here...

    }

    }



    Thursday, March 13, 2008 12:58 AM

Answers

  • I have to agree with Marc on this.  It is a common misconception that generics somehow relate to inheritance but they don't.  They are completely separate concepts.  A generic's sole purpose is to provide a standard implementation of some code irrelevant of the underlying type, such as a list.  By definition  List<X> is unrelated to all other List types unless X = Y.  The fact that they share the same underlying implementation is irrelevant.  Logistically they are separate types.  They are, in fact, equivalent to this:

     

    Code Snippet

    //List

    ListX
    {

       ...     
    }

     

    //List
    ListY
    {

       ...
    }

     

     

    C# (and most languages) require that equivalency be defined as having the same type (or type equivalency) rather than structural equivalency.  With structural equivalency two objects are assignable if they have the same internal structure.  For example C# does not allow this although there is no technical reason why it wouldn't work:

     

    Code Snippet

     

    struct A
    {
       int y;
    }

     

    struct B
    {

       int y;
    }

     

    A a;

    B b;

    a = b;

     

     

    Some languages, like Pascal, do.  How does this relate to the generic example?  Type equivalency.  The only place List<X> and List<Y> share a common reference is at the base class for each of them.  Therefore they are not interchangeable.  But wait, there's more...

     

    You could easily argue that since Y derives from X then you should be able to use Y in List<X>.  I would agree and you can do that.  This is perfectly legal:

     

    Code Snippet

    List<X> list;

    list.Add(new Y());

     

     

    But that is not what you are asking for.  You want to be able to copy an element of type List<Y> and treat it as List<X>.  We're back to type equivalency.  In fact arrays have this same issue.  This has never been legal in C# for the same reasons:

     

    Code Snippet

    void Foo ( X[] list ) { ... }

     

    Foo(new Y[] { });

     

     

    While I understand that there are occasions where this would be useful I believe the existing implementation works correctly and should not change.  If you really want to work with either type then you should use a more general type such as IList or something.  Not as convenient but safer.  IMHO.

     

    Michael Taylor - 3/13/08

    http://p3net.mvps.org

     

     

    Thursday, March 13, 2008 1:42 PM

All replies

  • That would be a great feature!

     

    I have run into this myself a couple of times. It seems that generics break polymorphism. This is something that should be corrected.

     

    Mike

     

    Thursday, March 13, 2008 1:08 AM
  • Because a list is its own type. SO List<B> doesnt inherit from List<A> so you cant convert.  You could easily create a helper class to do this though.  Just cycle through and add them to a List<A>.
    Thursday, March 13, 2008 1:08 AM
  • In Visual Studio 2008 has new features called Extensions for solving this kind of problem, so you can do this

     

    Code Snippet

    List<A> theAList = theBList.Cast<A>().ToList<A>();

     

     

    Thursday, March 13, 2008 2:38 AM
  • In an OO model - the request by Swim is a natural one - and the way things should work - if inheritance worked as expected.

     

    There are ways to work around the limitiation in the language as both Dan & Zaben pointed out - and though they will work - IMHO they're not the ideal solution to the problem - if we agree that a solution is required.

     

    The ideal solution for me would be to solve the limitiation of inheritance for generics so that it behaved as expected in an O-O world.

     

     

    Thursday, March 13, 2008 3:39 AM
  • That is very deliberate. Otherwise, DoSomething could call:

       

    Code Snippet
       theAList.Add(new A());

     

     

    Which cannot work since the list only allows Bs.. A common workaround in this case is a generic method:

     

    Code Snippet

    static void DoSomething<T>(List<T> theList) where T : A {

      // do something

    }

     

     

    Now you can call DoSomething(theBList), but inside DoSomething you have to think in terms of T

    Thursday, March 13, 2008 7:08 AM
  •  

    [Michael Sargent]

    It seems that generics break polymorphism. This is something that should be corrected.

     

    [paulc13]

    In an OO model - the request by Swim is a natural one - and the way things should work - if inheritance worked as expected.

     

    Sorry, but I have to disagree with both... standard polymorphism an inheritance only apply if you assume that B : A implies List<B> : List<A>, which is simply not the case.

     

    The keyword here would be generic covariance; simply put, it isn't supported (for good reasons). Note that rather confusingly array covariance is supported, but this is largely for legacy reasons, and is susceptible to the bug avoided in my last post.

    Thursday, March 13, 2008 7:17 AM
  • I have to agree with Marc on this.  It is a common misconception that generics somehow relate to inheritance but they don't.  They are completely separate concepts.  A generic's sole purpose is to provide a standard implementation of some code irrelevant of the underlying type, such as a list.  By definition  List<X> is unrelated to all other List types unless X = Y.  The fact that they share the same underlying implementation is irrelevant.  Logistically they are separate types.  They are, in fact, equivalent to this:

     

    Code Snippet

    //List

    ListX
    {

       ...     
    }

     

    //List
    ListY
    {

       ...
    }

     

     

    C# (and most languages) require that equivalency be defined as having the same type (or type equivalency) rather than structural equivalency.  With structural equivalency two objects are assignable if they have the same internal structure.  For example C# does not allow this although there is no technical reason why it wouldn't work:

     

    Code Snippet

     

    struct A
    {
       int y;
    }

     

    struct B
    {

       int y;
    }

     

    A a;

    B b;

    a = b;

     

     

    Some languages, like Pascal, do.  How does this relate to the generic example?  Type equivalency.  The only place List<X> and List<Y> share a common reference is at the base class for each of them.  Therefore they are not interchangeable.  But wait, there's more...

     

    You could easily argue that since Y derives from X then you should be able to use Y in List<X>.  I would agree and you can do that.  This is perfectly legal:

     

    Code Snippet

    List<X> list;

    list.Add(new Y());

     

     

    But that is not what you are asking for.  You want to be able to copy an element of type List<Y> and treat it as List<X>.  We're back to type equivalency.  In fact arrays have this same issue.  This has never been legal in C# for the same reasons:

     

    Code Snippet

    void Foo ( X[] list ) { ... }

     

    Foo(new Y[] { });

     

     

    While I understand that there are occasions where this would be useful I believe the existing implementation works correctly and should not change.  If you really want to work with either type then you should use a more general type such as IList or something.  Not as convenient but safer.  IMHO.

     

    Michael Taylor - 3/13/08

    http://p3net.mvps.org

     

     

    Thursday, March 13, 2008 1:42 PM
  • All good points; one minor correction:

    <q> This has never been legal in C# for the same reasons</q>

     

    Actually (and since we are talking about inheritance), if Y : X, then that is perfectly legal, and will work as long as you don't (in Foo) attempt to insert an X (since the actual array only allows Y). Or more specifically: array covariance is supprted, but it is the exception.

    Thursday, March 13, 2008 2:35 PM
  • I hope they eventually support covariance in C# (its actually supported by the CLR I'm told).  But, even when it does, List<Y> should not allowed to be treated as a List<X>, because List<Y> will not allow objects to be added to it that would be allowed in List<X>.  However, I think one should be able to treat an instance of IEnumerable<Y> as an instance of IEnumerable<X>, which is why I hope C# eventually supports generics.  See http://blogs.msdn.com/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx for a series of articles that explores the ins and outs of generic covariance and its possible future support in C#.
    Friday, March 14, 2008 6:23 PM
  • I think the real issue is using generic objects as parameters with non-generic methods.  A generic method works just as one would expect with the generic object parameter.

     

    Code Snippet

    private static void DoSomething<T>(List<T> theList) where T : A

    {

    return;

    }

     

     

    Rudedog

     

    Edit.  I do not consider this to be a work around.  This is simply how generic parameters should be used if you want to take advantage any inheritance in the types used as T.

    Friday, March 14, 2008 6:47 PM
  • A recent post about inheriting from List<>

     

    http://forums.microsoft.com/Forums/ShowPost.aspx?PostID=2937514&SiteID=1

     

    Rudedog

     

    Friday, March 14, 2008 7:43 PM
  • But, even when it does, List<Y> should not allowed to be treated as a List<X>, because List<Y> will not allow objects to be added to it that would be allowed in List<X>.


    Well, I've heard this before, but I disagree.

    Suppose this:

    Code Snippet

    List<string> stringList = new List<string>();
    stringList.Add("msdn forums");
    List<object> objectList = stringList; // This is not currently possible.

    // Now consider this:
    objectList.Add(new SomeClass());


    Allthough this might seem like adding a non-string to a string list, it's not.
    It's not, because it's an object list. The way I see it, the moment we cast the stringList into the objectList, we are also casting the internal T types. So the internal string array is also cast to an object array. This way, C-type casts and the is and as operator would also be allowed (Do consider that a objectList would only be considered being castable to a stringList if every T variable can be cast to string).

    EDIT: I meant "List<object> objectList = stringList;" instead of "List<object> objectList = stringList();"
    Monday, March 17, 2008 12:32 PM
  •  ThePatrickP wrote:
    But, even when it does, List<Y> should not allowed to be treated as a List<X>, because List<Y> will not allow objects to be added to it that would be allowed in List<X>.


    Well, I've heard this before, but I disagree.

    Suppose this:

    Code Snippet

    List<string> stringList = new List<string>();
    stringList.Add("msdn forums");
    List<object> objectList = stringList(); // This is not currently possible.

    // Now consider this:
    objectList.Add(new SomeClass());


    Allthough this might seem like adding a non-string to a string list, it's not.
    It's not, because it's an object list. The way I see it, the moment we cast the stringList into the objectList, we are also casting the internal T types. So the internal string array is also cast to an object array. This way, C-type casts and the is and as operator would also be allowed (Do consider that a objectList would only be considered being castable to a stringList if every T variable can be cast to string).

     

    Of course that is not possible.  You are using a variable as if it were a method.  I do not understand your argument or see the point you are trying to make. 

     

    Well, I agree with the original statement, in its original context.  List<Y> is a different type than List<X>.  No casts exists between those types unless the developer writes one.  There should not be any " automatic built-in" conversion either because it would next to impossible to write.  A conversion from Type Y to Type X.  How would you wirte that?

     

    The original post was about inheritance of List<T> objects.  Where the types used as T all share a common base class.  The OP complained that List<> did not follow the expected behavior of derived classes.  He wanted to specify the base class, List<baseClass>, as a method parameter so that he could then use that method with any List<derivedClass>.

    Monday, March 17, 2008 1:11 PM
  •  

    > Allthough this might seem like adding a non-string to a string list, it's not.

     

    Yes it is.

     

    Even if it compiled, the cast is only a cast. Nothing has been converted; the internals are still unchanged. The actual list is still a List<string> and is backed by a string array.

    If you want a separate, disconnected List<object> then create one and copy the data in.

     

    As for the last statement:

    > Do consider that a objectList would only be considered being castable to a stringList

    > if every T variable can be cast to string

     

    The whole point here is compile-time checking. You can't check contents at compile-time; this is simply unworkable.

    Monday, March 17, 2008 2:05 PM
  •  ThePatrickP wrote:
    But, even when it does, List<Y> should not allowed to be treated as a List<X>, because List<Y> will not allow objects to be added to it that would be allowed in List<X>.


    Well, I've heard this before, but I disagree.

    Suppose this:

    Code Snippet

    List<string> stringList = new List<string>();
    stringList.Add("msdn forums");
    List<object> objectList = stringList; // This is not currently possible.

    // Now consider this:
    objectList.Add(new SomeClass());


    Allthough this might seem like adding a non-string to a string list, it's not.
    It's not, because it's an object list. The way I see it, the moment we cast the stringList into the objectList, we are also casting the internal T types. So the internal string array is also cast to an object array. This way, C-type casts and the is and as operator would also be allowed (Do consider that a objectList would only be considered being castable to a stringList if every T variable can be cast to string).

    EDIT: I meant "List<object> objectList = stringList;" instead of "List<object> objectList = stringList();"

     

    So, in your scenario, are objectList and stringList still referencing the same list?  If so, then what happens when I call stringList[1]?  The element at index 1 isn't a string, its an instance of SomeClass, but stringList is only supposed to contain strings.

     

    If they they aren't pointing to the same list, then you're essentially arguing for a copy operation to occur so that future changes to the elements of one variables doesn't effect the other, which can already be done easily and without hidding the copy operation behind an assignment statement.

    Monday, March 17, 2008 6:34 PM
  • 0_o

    Oh damn didn't think of that Big Smile. You're right.
    I still think covariance would be good though.

    Btw, the thing about generics that bothers me the most is that visual studio 2008 *still* does not support generic forms and user controls.
    Tuesday, March 18, 2008 11:01 AM
  •  ThePatrickP wrote:
    thing about generics that bothers me the most is that visual studio 2008 *still* does not support generic forms and user controls.

     

    You are right; it is a bit irritating ;-p

     

    I don't know the MS position, but I've resigned myself that winforms is dead (at least as far as large IDE overhauls are concerned), and MS are focusing on WPF/silverlight. It would be nice, I agree... but... oh well.

    Tuesday, March 18, 2008 1:03 PM