1

I have 2 classes A and B. B inherits from A. If I have a method like void works(A aType) I can pass a B type object through just fine. However, a method like void fails(List<A> aListType) fails for a List<B> type with Visual Studio complaining that it can't convert between List<A> and List<B>. I can resolve this with generics, but why does it fail in the first place? Also, should I just be using generics instead?

Here's a basic example:

using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            B bType1 = new B();
            works(bType1); // This works

            List<B> bListType1 = new List<B>();
            works(bListType1); // This works

            List<B> bListType2 = new List<B>();
            fails(bListType2); // This fails
        }

        static void works(A aType)
        {
            return;
        }

        static void works<T>(List<T> aListType) where T : A
        {
            return;
        }

        static void fails(List<A> aListType)
        {
            return;
        }

    }

    class A
    {

    }

    class B : A
    {

    }
}
Ringil
  • 6,277
  • 2
  • 23
  • 37

3 Answers3

4

That's because List<T> is an Invariant type in C#, which means you can only use the type that was originally specified in the variable definition (i.e. A). You'll notice that if you replace List<A> with IEnumerable<A>, your error goes away. This is because IEnumerable<A> is Covariant, meaning you can use a more derived type than originally specified (i.e. B). Read more about Covariance and Contravariance in C# here.

Arian Motamedi
  • 7,123
  • 10
  • 42
  • 82
3

If you pass a List<B> into fails, and fails somewhere along the line tries to add an A to to it (because a List<A> has an void Add(A item) method), what would you expect to happen?

static void Fails(List<A> items)
{
    aListType.Add(new A());
}

...

Fails(new List<B>());

Let's say this code compiles; it will have to explode at runtime when it tries to add an A to a List<B>. Is this a type safe operation?

If you only want to iterate over the List<B> as a List<A>, you should use the IEnumerable<A> interface. A List<B> can be treated as an IEnumerable<A>, since the IEnumerable interface is covariant.

static void AlsoWorks(IEnumerable<A> items)
{
    foreach(var a in items)
    {
        Console.WriteLine("No problems here!");
    }
}

...

AlsoWorks(new List<B>());
Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • If this isn't considered safe, why is using `static void works(List aListType) where T : A` considered safe by the compiler? Doesn't that run into the same issue? – Ringil Sep 18 '15 at 10:20
  • @Ringil That generic method call is resolving to `static void works(List aListType)` when you pass in a `List`. The type argument provided for the parameter `T` is `B`. It should come as no surprise that passing a `List` where a `List` is expected is type safe. – Asad Saeeduddin Sep 18 '15 at 10:23
0

Marc Gravell has answered this before. The explanation is ideal for your understanding here of what options you have and why what you tried isnt possible.

https://stackoverflow.com/a/5832173/1347784

Community
  • 1
  • 1
phil soady
  • 11,043
  • 5
  • 50
  • 95