Search code examples
c#inheritanceabstract-classcovariancecontravariance

How to use child type where abstract parent type is expected


This may be more of a covariance/contravariance issue. But I have the following classes:

public interface IOutputPort<in TUseCaseResponse>
{
    void Handle(TUseCaseResponse response);
}

//Abstract parent
public abstract class Response
{
}

//Concrete child
public class GenericResponse : Response
{
}

internal class MockPresenter<TResponse> : IOutputPort<TResponse> where TResponse : Response
{
    public void Handle(TResponse response)
    {
    }
}

public class LocationStore
{
    public void DoThing(IOutputPort<Response> outputPort)
    {
    }
}

The idea is that LocationStore should be able to accept an IOutputPort object that has a generic type that at least inherits from Response

I then try to call my function similar to:

var genericPresenter = new MockPresenter<GenericResponse>();
var store = new LocationStore();

store.DoThing(genericPresenter);

But the compiler complains that Argument 1: cannot convert from 'MockPresenter<GenericResponse> to 'IOutputPort<Response>'

Even if I remove the contravariant modifier from IOutputPort I still get the same error. I might be missing something pretty obvious here, but I'm feeling a bit stuck. How do I design my DoThing method to accept an IOutputPort that can have any type inheriting from Response?

Passing in a generic as a type argument to LocationStore isn't an option, because the same instance needs to be able to call DoThing with several different output ports.


Solution

  • Well the easy solution is to just make DoThing generic:

    public void DoThing<TResponse>(IOutputPort<TResponse> outputPort) where TResponse : Response
    {
    }
    

    This makes it compile, and hopefully provide the functionality you need.

    As for why your design was not allowed consider the following:

    public class OutputPort<T>: IOutputPort<T>
    {
        private List<T> _innerList;
    
        public void Handle(T response)
        {
            _innerList.Add(response);
        }
    }
    

    Now if T is GenericResponse and you would be able to cast it to IOutputPort<Response> suddenly you would be able to add a Response to the List<GenericResponse> which hopefully is clear why it isn't allowed.