2

Assume there's a base class like this called a Cup:

public abstract class Cup { }

And let's assume there're PaperCup inheriting Cup and PlasticCup that inherits the PaperCup class.

public class PlasticCup : PaperCup { }
public class PaperCup : Cup { }

And assume that there're two methods in Main class.

static void Test01 (PaperCup cup) { }
static void Test02 (Cup cup) { }

TEST1

PaperCup A = new PaperCup();
Test01(A);
Test02(A);

Above code works fine. A instance can be passed into those two function because it is PaperCup itself and it inheris Cup base class.

TEST2

PlasticCup B = new PlasticCup();
Test01(B);
Test02(B);

Above code still works fine. B instance also is able to be taken by the functions although it is PlasticCup but it inheris PaperCup and it is eventually derived from Cup.

But Generics !!

Let's see following methods.

static void Test010 (IList<PaperCup> cup) { }
static void Test011(IList<PlasticCup> cup) { }
static void Test012(IList<Cup> cup) { }

And this trial below will fail at two method calls.

IList<PlasticCup> BB = new List<PlasticCup>();
Test010(BB); // Fail CS1503 Compiler Error
Test011(BB);
Test012(BB); // Fail CS1503 Compiler Error

Simple Question

Why is it impossible to pass the derived types into those functions when they take Generic? isn't it supposed to work because C# is an OOP language?

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
hina10531
  • 3,938
  • 4
  • 40
  • 59
  • 5
    Covariance: https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance – DavidG Dec 27 '17 at 01:01
  • "because C# is an OOP language?" I wonder how you got to this conclusion. C# is a multi-paradigm language, even a functional one. – Camilo Terevinto Dec 27 '17 at 01:13
  • @CamiloTerevinto That's not a great update imo, if you're going to edit, it's almost never a good idea to change code. – DavidG Dec 27 '17 at 01:25
  • @DavidG How so? I removed only the completely irrelevant code, making the question much shorter. – Camilo Terevinto Dec 27 '17 at 01:26
  • @CamiloTerevinto It would only take a very small change to the question for that code to become relevant though. The code caused no harm and removing it was wasteful. – DavidG Dec 27 '17 at 01:27
  • @DavidG Thank you for the link. The doc explains everything I was confused about. I should dig C# into harder I guess. – hina10531 Dec 27 '17 at 01:33
  • @hina10531 I think covariance/contravariance is probably one of the least known things about C#. – DavidG Dec 27 '17 at 01:34

1 Answers1

8

Ok, imagine the following were allowed:

IList<Cup> plasticCups = new List<PlasticCup>();

Then the following would be possible:

plasticCups.Add(new PaperCup()); //ouch!

And you've just inserted a paper cup in a plastic cup list. That doesn't seem right.

What you are asking about is generic type variance. IList<T> is invariant in T because any other option (covariant or contravariant) would be unsafe.

C# does allow generic type variance in interfaces and delegates but only when the compiler can make sure its safe.

For instance IEnumerable<out T> is covariant in T because you can't insert a paper cup into an IEnumerable<PlasticCup> (the rule is really that the type T is never used as a method argument), so the following is perfectly safe and will compile:

IEnumerable<Cup> plasticCups = Enumerable.Empty<PlasticCup>();

Its interesting to note that due to "supporting other existing language features" reasons when C# was born, arrays also support type variance but its completely broken. With arrays this is perfectly legal:

object[] strings = new string[] { .... }
strings[0] = new object(); //oh oh

And you will get an unfortunate runtime exception. With generics this can't happen.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • 1
    Minor point: It's not about "inserting" `T` into the generic type, it's about the generic type taking the type as an input. So yes, that could be the `Add` method, but it could be anything else. – DavidG Dec 27 '17 at 01:12
  • @DavidG good point! Adding is just the obvious example but yes, a `Find(T t)` would be equally unsafe. – InBetween Dec 27 '17 at 01:16
  • This is also a dupe but I'm trying to figure out the best target! :) – DavidG Dec 27 '17 at 01:23