10

I am trying to use polymorphism in generic type parameters in C#. I have reviewed several other questions on SO (1, 2, 3, 4, 5, 6), but I'm still not clear why this doesn't work (or if it's even allowed).

Background

I have the following classes:

public class Base { }

public class Derived<T> : Base { }

public class Foo { }

and an instance of my derived generic type:

var derived = new Derived<Foo>();

The basics

The following statements are all true:

derived is Object

derived is Base

derived is Derived<Foo>

The problem

When I try to use my derived class as a type parameter in another generic type I get some unexpected behavior. Given the following lazy instance:

var lazy = new Lazy<Derived<Foo>>();

The following is true:

lazy is Lazy<Derived<Foo>>

But these are false when I expected them to be true:

lazy is Lazy<Object>

lazy is Lazy<Base>

Why is this? Should they be true or have I misunderstood how generics work?

Kevin Kuszyk
  • 1,958
  • 24
  • 37
  • Well a `Whatever` isn't the same type as a `Whatever`. – DavidG Jul 26 '17 at 09:57
  • `derived is Derived` is true but `derived is Derived` is false. The same applies to lazy too. – Chetan Jul 26 '17 at 09:59
  • @DavidG @Chetan Ranpariya `Something` or `Foo` are both `Object`s. Can they not be substituted for `Object` in the type parameter? – Kevin Kuszyk Jul 26 '17 at 10:05
  • Yes, they are `object` but they are not `Whatever` – DavidG Jul 26 '17 at 10:06
  • Consider the difference between `List` and `List`. While it is true that anything that can be stored in the first can also be stored in the last, they are still considered very different things, stored in different ways. Putting a string in a `List` and then retrieving it will return a value of type `object`, not `String`. The generic type is not the defining trait of the type. It is "a **list** of strings", rather than "a list of **strings**". It sounds pedantic, but there is a notable difference to consider. – Flater Jul 26 '17 at 12:05

2 Answers2

8

Yes, you misunderstood how generic works. This is as well the biggest limitation to usage of Generic types (in fact you should avoid them as much as possible because of that). If Derived inherits from Base then it is normally not true that Generic<Derived> is Generic<Base>. The exception to this is covariance and contravariance. In C# it works only with interfaces and delegate types. If you define your Generic interface like:

public interface Generic<out T> {}

then Generic<Derived> is Generic<Base>

If you define your Generic class like:

public interface Generic<in T> {}

then Generic<Base> is Generic<Derived> (surprise, huh?).

Why the simple cast does not work? Imagine object of a class implementing interface that looks as follows:

public interface Generic<T> 
{
    public void Func1(T input);
    public T Func2();
}

Imagine we have Generic<Derived> object and we are using it as Generic<Base>. In this case Func2 works perfectly - it returns Derived object which can be caster to Base. But Func1 won't work - we have a function that accepts Base object but the actual object has Func1 that accepts only Derived objects and not all Base objects are Derived, right?

This example explains why with in and out inheritance works. If we apply in constraint on type parameter in generic class we commit that T type may only be returned from properties or functions, but it may never be accepted as parameter. In such case our Generic interface looks like this:

public class Generic<out T> 
{
    public T Func2();
}

As we exaplained in previously Func2 will work fine if we will use Generic<Derived> object as Generic<Base>. For the same reason for an interface:

public interface Generic<in T> 
{
    public void Func1(T input);
}

Func1 will work fine if object Generic<Base> will be used as Generic<Derived> - in this case we will always pass to Func1 Derived objects as parameters and Dervied is always Base by definition.

mr100
  • 4,340
  • 2
  • 26
  • 38
  • 1
    We should avoid generic types? – pinkfloydx33 Jul 26 '17 at 11:19
  • 1
    They often mean a huge burden because of the contradiction mentioned in the question (unfortunately). This is as inheritance - it is great to apply it, but only when really needed. Usually inheritance is worse option than composition. But of course there are certain cases when it is best solution. I think the same applies to generics. Just use them carefully and only if you really need them. From my experience generics tends to get you in trouble if you apply them for really complex classes. – mr100 Jul 26 '17 at 11:23
  • To be precise - I never said that generic types are bad. Sometimes they are blessing. But I've seen several times when they caused head aches after applying them without caution. – mr100 Jul 26 '17 at 11:37
  • 2
    @mr100 I tried this in LinqPad with classes, but got a compile error. I found it only worked with interfaces. – Kevin Kuszyk Aug 02 '17 at 08:24
  • Yes, indeed, very vital remark. Thank you, I will update the answer with those details of yours. Truly this works only with interface and delegate types (which on the other hand makes sense). Thus it is impossible to achieve what you want with generic classes. – mr100 Aug 02 '17 at 09:52
  • Why is this answer upvoted and accepted when the examples do not work? "Invalid variance modifier. Only interface and delegate type parameters can be specified as variant." – Nicholas Petersen Jun 08 '20 at 18:47
0
IList<string> derivedList = new List<string>();    
IList<object> baseList = derivedList;

The above code won't compile.

The polymorphism rules only apply to classes' inheritance, not applied to type parameters.

template is not polymorphic (in cpp), same generic type is not polymorphic (in c#)

Gen.L
  • 382
  • 4
  • 12