简体   繁体   中英

Casting to base interface when using covariance and multiple generic parameters

I'm trying to make all these mapper classes have a common base:

// base
class BaseInput { public string BaseInputValue { get; set; } }
class BaseOutput { public string BaseOutputValue { get; set; } }

interface IMapper<InputType, out OutputType>
    where InputType : BaseInput
    where OutputType : BaseOutput
{
    OutputType Map(InputType input);
}

// example implementation
class Input : BaseInput { public string InputValue { get; set; } }
class Output : BaseOutput { public string OutputValue { get; set; } }

class MyMapper : IMapper<Input, Output>
{
    public Output Map(Input input)
    {
        // custom mapping done here to Output
        return new Output { /* mapping */ };
    }
}

This code to create a new one of these mappers and assign it to the base compiles fine:

var myBaseMapper = (IMapper<BaseInput, BaseOutput>) new MyMapper();

But I get a runtime error:

Unable to cast object of type 'MyMapper' to type 'IMapper`2[UserQuery+BaseInput,UserQuery+BaseOutput]'.

If I reduce IMapper to IMapper<out OutputType> it works fine, but then that requires doing a cast in MyMapper.Map , which is slightly annoying to have to do in each mapper class. Additionally, that makes me lose the information for deciding which Mapper to use for what BaseInput and so I have to define that elsewhere.

Is doing this just not possible in C# or is there a way of doing something similar? If not, I will have to rethink my design.

You can't do this because it doesn't make sense, your interface is contravariant in the InputType parameter (or, rather, it would be if you added the in keyword to that parameter). To see why this doesn't make sense, let's look at your example:

You want an implementation of IMapper<Input, Output> to be assignable to IMapper<BaseInput, BaseOutput> . Let's say we create some new child class of BaseInput , we'll call it MoreInput :

class MoreInput : BaseInput
{
    public string LotsOfInput { get; set; }
}

Okay, now let's say we have a method whose body looks something like this (and that what you wanted actually worked):

IMapper<BaseInput, BaseOutput> mapper = new MyMapper();
mapper.Map( new MoreInput() );

Well, at this point nothing's wrong: IMapper<BaseInput, BaseOutput> has a Map method that accepts BaseInput as its parameter and MoreInput is a BaseInput , so our call to Map is valid.

Except it's not, because the Map method we're really calling expects an Input as its argument and MoreInput is not Input . We've broken the type system.

This is what the compiler is telling you when it doesn't let you make that assignment implicitly: that conversion isn't safe. The compiler can't guarantee that the types you expect are the types you get.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM