Polymorphism in generic type parameters
Asked Answered
V

2

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?

Visigoth answered 26/7, 2017 at 9:51 Comment(5)
Well a Whatever<Something> isn't the same type as a Whatever<object>.Impediment
derived is Derived<Foo> is true but derived is Derived<Object> is false. The same applies to lazy too.Mcgannon
@Impediment @Mcgannon Ranpariya Something or Foo are both Objects. Can they not be substituted for Object in the type parameter?Visigoth
Yes, they are object but they are not Whatever<object>Impediment
Consider the difference between List<String> and List<object>. 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<object> 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.Gonococcus
S
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.

Somerville answered 26/7, 2017 at 10:4 Comment(6)
We should avoid generic types?Torbart
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.Somerville
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.Somerville
@Somerville I tried this in LinqPad with classes, but got a compile error. I found it only worked with interfaces.Visigoth
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.Somerville
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."Apostrophize
D
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#)

Destinee answered 14/5, 2020 at 2:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.