60

I'm trying to pass A list of DerivedClass to a function that takes a list of BaseClass, but I get the error:

cannot convert from 
'System.Collections.Generic.List<ConsoleApplication1.DerivedClass>' 
to 
'System.Collections.Generic.List<ConsoleApplication1.BaseClass>'

Now I could cast my List<DerivedClass> to a List<BaseClass>, but I don't feel comfortable doing that unless I understand why the compiler doesn't allow this.

Explanations that I have found have simply said that it violates type safety somehow, but I'm not seeing it. Can anyone help me out?

What is the risk of the compiler allowing conversion from List<DerivedClass> to List<BaseClass>?


Here's my SSCCE:

class Program
{
    public static void Main()
    {
        BaseClass bc = new DerivedClass(); // works fine
        List<BaseClass> bcl = new List<DerivedClass>(); // this line has an error

        doSomething(new List<DerivedClass>()); // this line has an error
    }

    public void doSomething(List<BaseClass> bc)
    {
        // do something with bc
    }
}

class BaseClass
{
}

class DerivedClass : BaseClass
{
}

6 Answers6

80

It is because List<T> is invariant, not covariant, so you should change to IEnumerable<T> which supports covariant:

IEnumerable<BaseClass> bcl = new List<DerivedClass>();

public void doSomething(IEnumerable<BaseClass> bc)
{
    // do something with bc
}

Information about covariant in generic.

Community
  • 1
  • 1
cuongle
  • 74,024
  • 28
  • 151
  • 206
  • 4
    Starting from .Net 4.5, there is also `IReadOnlyList` that can be used if the method needs random access to the collection element. – tia Jun 06 '13 at 16:21
62

Explanations that I have found have simply said that it violates type safety somehow, but I'm not seeing it. What is the risk of the compiler allowing conversion from List<DerivedClass> to List<BaseClass>?

This question is asked almost every day.

A List<Mammal> cannot be converted to a List<Animal> because you can put a lizard into a list of animals. A List<Mammal> cannot be converted to a List<Giraffe> because there might be a tiger in the list already.

Therefore List<T> has to be invariant in T.

However, List<Mammal> can be converted to IEnumerable<Animal> (as of C# 4.0) because there is no method on IEnumerable<Animal> that adds a lizard. IEnumerable<T> is covariant in T.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 6
    +1 A simple zoological explanation that explains it all. (there is no method on IEnumerable that adds a lizard --> I'll check just in case ;)) – vc 74 Jun 06 '13 at 16:32
  • 4
    I feel dumb... the giraffe one seems obvious, but why shouldn't you be able to pass mammals to something that can handle animals, just because it can handle non-mammals too? – MGOwen Sep 24 '16 at 06:29
  • 6
    @MGOwen: You have a list of mammals. You decide to treat it as a list of animals. You put a lizard into the list of animals. **But it is really a list of mammals**. Now you have a list of mammals with a lizard in it, violating the expectation that a list of mammals has only mammals in it. The conclusion is a violation of the type system, so one of those steps must be illegal. It is the first one: you cannot treat a list of mammals as a list of animals. – Eric Lippert Sep 24 '16 at 12:30
  • I still don't get this explanation. If I start with a list of *Mammals*, shouldn't I be able to convert it to a list of Animals, as all Mammals are Animals? Why does it matter that I can add Lizards to this **new** list of Animals? I don't care that it used to be a list of *Mammals*, it's not anymore, isn't it?. – Ryan Pergent Feb 14 '21 at 18:03
  • 3
    @RyanPergent: It's not a new list. It is the same list. `List` is a *reference type*; a reference conversion *reinterprets the meaning* of the reference. It doesn't change what object the reference refers to. – Eric Lippert Feb 15 '21 at 16:41
  • 3
    @RyanPergent: `mammals.Cast().ToList()` would be a new `List` that has the same contents as `mammals` but that conversion requires building a copy of the list and so is slow. A reference conversion can literally be no time at all if the optimizer is smart because a reference conversion changes *nothing* except for how the reference is interpreted. – Eric Lippert Feb 15 '21 at 16:45
  • I'm late to the party, but still don't understand something: Based on all of the above reasoning, why then am I able to use IEnumarable rather than List? There may still be giraffes, lizards and other things in it. Why does it all just work simply because I've changed the signature of the target method? – Michael Rodrigues Jun 20 '22 at 02:55
  • 3
    @MichaelRodrigues great question. Notice that IEnumerable only allows you to *read* the sequence. It does not allow you to *write* it. That's why it can be made safe for covariance. Since there is no way *via `IE` to *insert* a Tiger into an `IE`, it doesn't matter if it is *really* an `IE`. – Eric Lippert Jun 23 '22 at 03:15
  • `IReadOnlyList` or `IReadOnlyCollection` works too now. – Sedat Kapanoglu Apr 19 '23 at 19:24
15

The behavior you're describing is called covariance – if A is B, then List<A> is List<B>.

However, for mutable types like List<T>, that is fundamentally unsafe.

Had this been possible, the method would be able to add a new OtherDerivedClass() to a list that can actually only hold DerivedClass.

Covariance is safe on immutable types, although .Net only supports it in interfaces and delegates.
If you change the List<T> parameter to IEnumerable<T>, that will work

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • @SamIam: No; you're trying to pass a `List`. Passing a `List` that contains `Derived`s would work fine. – SLaks Jun 06 '13 at 16:15
  • So if I cast or convert my `List` to `List` before passing it in would that be okay? – Sam I am says Reinstate Monica Jun 06 '13 at 16:16
  • 1
    @SamIam: No. The whole point is that it is fundamentally unsafe to cast `List` to `List`. If you create a `List` to begin with, it will work. – SLaks Jun 06 '13 at 16:17
  • @SamIam: SLaks explained it well: if you *could* cast a `List` to a `List`, then you could theoretically add an instance of `OtherDerived` to it (because the list is *mutable*). **But it was instantiated as a `List`**, and if you tried to manually cast it back to `List`, you'd get a `List` with an `OtherDerived` in it. – voithos Jun 06 '13 at 16:28
2

When you have a class derived from a base class, any containers of those classes are not automatically derived. So you cannot just cast a List<Derived> to a List<Base>.

Use .Cast<T>() to create a new list where each object is cast back to the base class:

List<MyDerived> list1 = new List<MyDerived>();
List<MyBase> list2 = list1.Cast<MyBase>().ToList();

Note that this is a new list, not a cast version of your original list, so operations on this new list will not reflect on the original list. Operations on the contained objects will reflect, however.

Matt Houser
  • 33,983
  • 6
  • 70
  • 88
1

If you could write

List<BaseClass> bcl = new List<DerivedClass>(); 

you could call

var instance = new AnotherClassInheritingFromBaseClass();
bc1.Add(instance);

Adding an instance which is not a DerivedClass to the list.

vc 74
  • 37,131
  • 7
  • 73
  • 89
0

A solution i used was to create an extension class:

public static class myExtensionClass 
{
    public void doSomething<T>(List<BaseClass> bc) where T: BaseClass
    {
        // do something with bc
    }
}

It uses generic, but when you call it you don't have to especify the class since you already 'told' the compiler the type is the same of extended class.

You would call it this way:

List<DerivedClass> lst = new List<DerivedClass>();
lst.doSomething();
Marlon
  • 1,719
  • 3
  • 20
  • 42