Hide generic type arguments behind concrete type results in CS0535

37 views Asked by At

I've a rather academic question: why is it technically not possible to use the following code?

// abstractions
public record MappingRequest<TContract, TEntity>(
    TContract Contract, TEntity Entity);

public interface IMapper<TResult, TContract, TEntity>
{
    TResult Map(MappingRequest<TContract, TEntity> request);
}

// more readable abstraction
public interface ILocationMapper
    : IMapper<LocationResult
    , LocationContract, LocationEntity>
{
}

// concrete
public record LocationResult;
public record LocationContract;
public record LocationEntity;

public record LocationRequest(LocationContract Contract, LocationEntity Entity)
    : MappingRequest<LocationContract
    , LocationEntity>(Contract, Entity);

public class LocationMapper : ILocationMapper
{
    public LocationResult Map(LocationRequest request)
    {
        return new LocationResult();
    }
}

This gives me the following compiler error:

Error CS0535 : 'LocationMapper' does not implement interface member 'IMapper<LocationResult, LocationContract, LocationEntity>.Map(MappingRequest<LocationContract, LocationEntity>)'

Why can't the compiler not "infer" (not entirely sure whether this is the correct term here) that LocationRequest inherits from MappingRequest?

1

There are 1 answers

0
Johnathan Barclay On BEST ANSWER

Your LocationMapper.Map method currently accepts a LocationRequest, but any implementation of IMapper<LocationResult, LocationContract, LocationEntity> must implement a Map method that accepts any MappingRequest<TContract, TEntity>, not just LocationRequest.

Your code is simple, but imagine LocationRequest had a property Foo:

public record LocationRequest(LocationContract Contract, LocationEntity Entity)
    : MappingRequest<LocationContract, LocationEntity>(Contract, Entity)
{
    public string Foo { get; }
}

And your method tried to use that property:

public class LocationMapper : ILocationMapper
{
    public LocationResult Map(LocationRequest request)
    {
        Console.WriteLine(request.Foo);
        return new LocationResult();
    }
}

Now, if you try to call that as an IMapper<LocationResult, LocationContract, LocationEntity>, with another MappingRequest<TContract, TEntity>, we have a problem, because there is no Foo property.

e.g.

var request = new MappingRequest<TContract, TEntity>(default, default);
IMapper<LocationResult, LocationContract, LocationEntity> mapper
    = new LocationMapper();
mapper.Map(request); // We don't have any Foo to use !!

The problem is that LocationRequest isn't a simplified alias for MappingRequest<LocationContract, LocationEntity>, it's a completely different type altogether.

If verbosity is a problem for you, then you can alias your type like this:

using LocationRequest = MappingRequest<LocationContract, LocationEntity>;