0

I'm having difficulties understanding how to use subtypes of a generic type.

Suppose the following situation

public abstract class A { }
public class B : A { }
public abstract class WrapperA<T>  where T :A { }
public class WrapperB : WrapperA<B> { }

I have a generic class WrapperA<T> that has been contstrain to only have class A or cubtypes of class A as generic type T

I also have a class UserA that requires a WrapperA element in it's constructor.

public abstract class UserA
{
    protected UserA(WrapperA input) { }
}

I tried the above, but that won't fly. This gives me the error:

Error CS0305 Using the generic type 'WrapperA<T>' requires 1 type arguments

I actually don't care which specific WrapperA object I get here, but I can set it to the parentclass A as I'm sure that the generic parameter will either be A, or a subtype of A, so we adjust as follows

public abstract class UserA
{
    protected UserA(WrapperA<A> input) { }
}

Fine, no more errors so far. But now I want to make a subclass of UserA , UserB that needs a 'WrapperB' as input.

public class UserB : UserA
{
    protected UserB(WrapperB input) : base(input) { }
}

Now this gives me the error:

Error CS1503 Argument 1: cannot convert from 'WrapperB' to 'WrapperA<A>'

Why can't it convert WrapperB to WrapperA<A> I would think that should be possible, since WrapperB is a subtype of WrapperA and the type parameter B is a subtype of A (which it must be because of the constraint)

Is my reasoning flawed, or is this just a limitation of the compiler?

If found a workaround by defining an interface public interface IWrapperA and letting my IWrapperA implement this interface. Now i can use the IWrapperA as a parameter in my constructor, but this feels like a hacky workaround that I shoudn't need

public interface IWrapperA { }

public abstract class WrapperA<T> : IWrapperA where T :A { }

public abstract class UserA
{
    protected UserA(IWrapperA input) { }
}
public class UserB : UserA
{
    protected UserB(WrapperB input) : base(input) { }
}

What would be a better, more elegant way to solve this problem?

Geert Bellekens
  • 12,788
  • 2
  • 23
  • 50
  • 1
    Does this answer your question? [Covariance error in generically constrained class](https://stackoverflow.com/questions/50935140/covariance-error-in-generically-constrained-class) – shingo Apr 21 '23 at 11:27
  • @shingo According to (the last sentence of) this answer it should work as I would expect it if a class constraint was used (which I did. Even stronger I constrained to classes of type `A`) – Geert Bellekens Apr 21 '23 at 11:39
  • 2
    Let's say I have an `Fruit` class and I define classes `Apple` and `Banana` that inherit from `Fruit`. Now if I define a class `BowlOfApples : List` I cannot cast an instance of `BowlOfApples` to `List` as it would allow be to add a `Banana` to a `BowlOfApples`. That's what you're doing here. – Enigmativity Apr 21 '23 at 11:44
  • Try with [class](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters) constraint. – Alessandro D'Andria Apr 21 '23 at 11:44
  • 1
    He meant `where T : class`, and the question is about interface, classes cannot be covariant at this moment. – shingo Apr 21 '23 at 11:45
  • @AlessandroD'Andria I already have a class constraint by contraining to type `A` – Geert Bellekens Apr 21 '23 at 11:55
  • 1
    @Enigmativity yes, thank you that explains it in terms I can understand :) – Geert Bellekens Apr 21 '23 at 11:58

1 Answers1

1

Thanks to the comment of @Enigmativity I understand why I'm not allowed to cast an object of type WrapperB to type WrapperA<A>

This can be explained using Fruit.

Suppose the following classes

public abstract class Fruit { }
public class Apple : Fruit  { }
public class Banana : Fruit  { }
public class BowlOfApples : List<Apple> {}

The WrapperB class is similar to a BowlOfApples whereas the WrapperA<A> class is similar to List<Fruit>.

Now if I would be allowed to convert object of type BowlOfApples to List<Fruit>, I could potentially add Bananas to my BowlOfApples

var myBowlOfApples = new BowlOfApples();
var myBanana = new Banana();
((List<Fruit>)myBowlOfApples).Add(myBanana);

Which would defeat the whole purpose of defining my BowlOfApples as a List<Apple>. I would no longer be certain my BowlOfApples only contained Apples

Geert Bellekens
  • 12,788
  • 2
  • 23
  • 50