Freek van Zee
Freek van Zee

Reputation: 21

Generic abstract argument for method C#

I have this code example. And I don't get why it does not compile. It does not allow to convert from D to A<B>.

D inherits A<C>. C inherits B, so that would mean I should be able to pass D into a A<B> method argument right? I'm probably missing something obvious here.

using System.Collections.Generic;

public class Program
{
    public void Main()
    {
        var d = new D();
        Foo(d);  //Cannot convert D to A<B>

    }

    private void Foo(A<B> bar)
    {

    }   
}

public abstract class A<TypeB> where TypeB : B
{
     public abstract List<TypeB> list { get; set; } 
}

public abstract class B
{
}

public class C : B
{

}

public class D : A<C>
{
     public override List<C> list { get; set; }
}

Upvotes: 2

Views: 254

Answers (1)

Eric Lippert
Eric Lippert

Reputation: 660004

This question has been asked hundreds of times on this site.

Suppose you have a bowl of fruit. Is it a bowl of apples? An apple is a fruit, so why is a bowl of fruit not a bowl of apples? Obviously because a bowl of fruit could already contain a banana, and so is not a bowl of apples.

Suppose you have a bowl of apples. Is it a bowl of fruit? You want to say "an apple is a fruit, therefore a bowl of apples is a bowl of fruit". But this is wrong. A bowl of fruit is something you can put a banana into, and since there is an operation you can perform on a bowl of fruit that you cannot perform on a bowl of apples, a bowl of apples cannot be used in a context where a bowl of fruit is expected.

The technical name for the kind of conversion you want is a "generic covariant conversion". C# supports generic covariant conversions in only a few specific cases:

  • The "outer" type must be an interface or delegate, for example, IEnumerable<T>.
  • The "inner" type must be a reference type, like string, not a value type, like int.
  • The "outer" type must be marked as safe for covariance; this is checked by the compiler.

You can use an IEnumerable<Apple> in a context where IEnumerable<Fruit> is expected because there is no way to put a banana into an IEnumerable<Fruit>. But you cannot use your class A<Apple> as an A<Fruit> because the compiler is not convinced that you have prevented all possibility of another type Banana that extends Fruit being used in a method that can only accept an Apple.

Do a search on this site for "covariance and contravariance" and you will find plenty of questions about this.

Upvotes: 9

Related Questions