3

I'm trying to implement an interface inheritance system in my C# project, but I can't get it to work.

Here is a simplified version:

public interface BaseInterface {}

public abstract class AbstractClass<T> where T : BaseInterface {}

public interface ChildInterface : BaseInterface {}

public class ConcreteClass : AbstractClass<ChildInterface> {}

I want to use it as follow:

AbstractClass<BaseInterface> c = new ConcreteClass();

The last line of code gives me the following error:

Cannot implicitly convert type 'ConcreteClass' to 'AbstractClass<BaseInterface>'

Why is the conversion impossible?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Yazhmog
  • 33
  • 2
  • Does the issue occur **only** if `AbstractClass` has generics involved? What might that tell you? – mjwills Jun 12 '19 at 23:16
  • 3
    `AbstractClass` is invariant, and you want a covariant type. See [Covariance and Contravariance](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance) in generics. You're limited to interfaces for co/contravariance, so you aren't going to be able to do what you want with an abstract class. – Jonathon Chase Jun 12 '19 at 23:29

2 Answers2

8

Let's have a play with your types and call them something different.

public interface IFruit { }

public abstract class BowlOf<Fruit> where Fruit : IFruit
{
    public void Add(Fruit fruit) { }
}

public class Apple : IFruit { }

public class BowlOfApples : BowlOf<Apple> { }

Now, with that - which is pretty much just a rename of the types (but changing public interface ChildInterface : BaseInterface {} to public class Apple : IFruit { }) then we create the following issue.

Let's say I have public class Banana : IFruit { } also and let's assume that the following is legal:

BowlOf<IFruit> c = new BowlOfApples();

Then I am perfectly fine to call c.Add(new Banana()). What!?! You can't add a banana to a bowl of apples.

And that's why the compiler is complaining when you try to do AbstractClass<BaseInterface> c = new ConcreteClass();.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
3

You aren't able to make the assignment because the base class, AbstractClass<T>, is invariant. What you want to be able to make that kind of assignment is a covariant type. Defining Covariance and Contravariance is limited to interfaces, so that means we need another interface.

public interface IAbstractClass<out T> where T : BaseInterface { }  
public abstract class AbstractClass<T> : IAbstractClass<T> where T : BaseInterface { }

The out keyword marks the generic type parameter as covariant. We then implement that interface in AbstractClass<T>, and our other types can work expected through the interface. These are also the only alterations we need to make, we leave the other type definitions the same:

public interface BaseInterface { }
public interface ChildInterface : BaseInterface { }

public class ConcreteClass : AbstractClass<ChildInterface> { }

We now have a covariant interface that AbstractClass<T> implements, and you can do the kind of assignment you desire, but you'll have to target the IAbstractClass interface.

public void Main() {
    IAbstractClass<BaseInterface> c = new ConcreteClass();
}
Jonathon Chase
  • 9,396
  • 21
  • 39