23

I defined the following class:

public abstract class AbstractPackageCall
    {

     ...

    }

I also define a subclass of this class:

class PackageCall : AbstractPackageCall
    {

      ...
    }

There are also several other subclases of AbstractPackageCall

Now I want to make the following call:

 List<AbstractPackageCall> calls = package.getCalls();

But I always get this exception:

Error   13  Cannot implicitly convert type 'System.Collections.Generic.List<Prototype_Concept_2.model.PackageCall>' to 'System.Collections.Generic.List<Prototype_Concept_2.model.AbstractPackageCall>' 

What is the problem here? This is the method Package#getCalls

 internal List<PackageCall> getCalls()
        {
            return calls;
        }
Jon
  • 428,835
  • 81
  • 738
  • 806
  • 2
    If you can return an `IEnumerable` instead of `List` then this will work as `IEnumerable<>` supports covariance. – Russ Cam Jan 10 '11 at 23:58
  • 1
    You may also be interested in reading these two sites. Reading this made co-/contra- variance click for me [C# 4.0 FEATURE FOCUS – PART 4 – CO- AND CONTRA-VARIANCE FOR GENERIC DELEGATE AND INTERFACE TYPES](http://community.bartdesmet.net/blogs/bart/archive/2009/04/13/c-4-0-feature-focus-part-4-generic-co-and-contra-variance-for-delegate-and-interface-types.aspx) and of course [Eric Lippert](http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx) has a lot of posts on the topic. – Roman Jan 11 '11 at 00:01
  • Thanks for all the answers, finally I think I understand the concept of covariance. –  Jan 11 '11 at 01:13
  • It's called covariance/contravariance with Generics. Check out [this SO question](http://stackoverflow.com/questions/245607/how-is-generic-covariance-contra-variance-implemented-in-c-4-0). – jro Jan 10 '11 at 23:55

5 Answers5

59

The simplest way to understand why this is not allowed is the following example:

abstract class Fruit
{
}

class Apple : Fruit
{
}

class Banana : Fruit
{
}

// This should intuitively compile right? Cause an Apple is Fruit.
List<Fruit> fruits = new List<Apple>();

// But what if I do this? Adding a Banana to a list of Apples
fruits.Add(new Banana());

The last statement would ruin the type safety of .NET.

Arrays however, do allow this:

Fruit[] fruits = new Apple[10]; // This is perfectly fine

However, putting a Banana into fruits would still break type safety, so therefor .NET has to do a type check on every array insertion and throw an exception if it's not actually an Apple. This is potentially a (small) performance hit, but this can be circumvented by creating a struct wrapper around either type as this check does not happen for value types (because they can't inherit from anything). At first, I didn't understand why this decision was made, but you'll encounter quite often why this can be useful. Most common is String.Format, which takes params object[] and any array can be passed into this.

In .NET 4 though, there's type safe covariance/contravariance, which allows you to make some assignments like these, but only if they're provably safe. What's provably safe?

IEnumerable<Fruit> fruits = new List<Apple>();

The above works in .NET 4, because IEnumerable<T> became IEnumerable<out T>. The out means that T can only ever come out of fruits and that there's no method at all on IEnumerable<out T> that ever takes T as a parameter, so you can never incorrectly pass a Banana into IEnumerable<Fruit>.

Contravariance is much the same but I always forget the exact details on it. Unsurprisingly, for that there's now the in keyword on type parameters.

JulianR
  • 16,213
  • 5
  • 55
  • 85
  • 19
    Why the downvote? If an answer isn't stupid, argumentative or offensive, always leave a comment so I could perhaps fix/correct things. – JulianR Jan 22 '11 at 19:05
  • That's a great answer right there. Fair play to you for taking the time – John Mc Mar 19 '13 at 22:47
2

Why what you try does't work

You are asking the compiler to treat a List<PackageCall> as a List<AbstractPackageCall>, which it is not. It's another matter that each of those PackageCall instances is in fact an AbstractPackageCall.

What would work instead

var calls = package.getCalls().Cast<AbstractPackageCall>().ToList();

Why what you try will never be allowed to work

Even though each item inside the return value of package.getCalls() derives from AbstractPackageCall, we can't treat the whole list as List<AbstractPackageCall>. Here's what would happen if we could:

var calls = package.getCalls(); // calls is List<PackageCall>
List<AbstractPackageCalls> apcs = calls; // ILLEGAL, but assume we could do it

apcs.Add(SomeOtherConcretePackageCall()); // BOOM!

If we could do that, then we could add a SomeOtherConcretePackageCall to a List<PackageCall>.

Jon
  • 428,835
  • 81
  • 738
  • 806
2

Now if you want to make the following call:

List<PackageCall> calls = package.getCalls();

// select only AbstractPackageCall items
List<AbstractPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());

I call this generalization.

Also, you can use this more specific:

List<PackageCall> calls = package.getCalls();

// select only AnotherPackageCall items
List<AnotherPackageCall> calls = calls.Select(); 
calls.Add(new AnotherPackageCall());

Implement this using extension method:

public static class AbstractPackageCallHelper
{
    public static List<U> Select(this List<T> source)
        where T : AbstractPackageCall
        where U : T
    {
        List<U> target = new List<U>();
        foreach(var element in source)
        {
            if (element is U)
            {
                 target.Add((U)element);
            }
        }
        return target;
    }
}
Alan Turing
  • 2,482
  • 17
  • 20
1

Because List<PackageCall> does not inherit from List<AbstractPackageCall>. You can use the Cast<>() extension method, like:

var calls = package.getCalls().Cast<AbstractPackageCall>();
Brian Ball
  • 12,268
  • 3
  • 40
  • 51
0

You cannot perform this conversion, because a List<AbstractPackageCall> can contain anything that derives from AbstractPackageCall, while a List<PackageCall> cannot -- it can only contain things that derive from PackageCall.

Consider if this cast were allowed:

class AnotherPackageCall : AbstractPackageCall { }

// ...

List<AbstractPackageCall> calls = package.getCalls();
calls.Add(new AnotherPackageCall());

You just added an AnotherPackageCall into a List<PackageCall> -- but AnotherPackageCall does not derive from PackageCall! That's why this conversion is not allowed.

cdhowie
  • 158,093
  • 24
  • 286
  • 300