I've been trying to work out how to get an equivalent of the following Java code in C# (Command is a functional interface).
public interface Executor<C extends Command> {
void execute(final C command) throws Exception;
}
The way my code is currently designed in the Java version, it is necessary for the type C to extend Command, which by my understanding is handled with covariance in C#.
However according to the C# docs, something like the following won't work because "The type is used only as a return type of interface methods and not used as a type of method arguments"
interface IExecutor<out Command>
{
void Execute(Command command);
}
Is there a way of specifiying that the type of the parameter for a method must be the covariant to the type of the interface in C#?
I'm relatively new to C#, so it could be that this is an XY problem, but I haven't found a solution that would work so far.
I think what you're after is a generic type constraint:
interface IExecutor<T> where T : Command
{
void Execute(T command);
}
This says that T
can be anything, so long as it extends the Command
class.
Covariance in C# is something a bit different to this, and it's about conversions between different types (arrays, generics, and delegates).
For example, IEnumerable is declared as IEnumerable<out T> { ... }
, which makes it covariant. This is a promise to the compiler that you will only ever taken items out of the IEnumerable<T>
, and never put them in.
This means that it's safe to write e.g.
IEnumerable<object> x = new List<string>();
Since you can only ever take strings out of the IEnumerable
, it's safe to pretend they're all objects. If you were allowed to put items into the IEnumerable
, then you could put any old object into a collection which only allows string
, which would be unsafe.
To take your example, because you only ever put Commands
into your IExecutor
, you could declare it as contravariant:
interface IExecutor<in T> where T : Command
{
void Execute(T command);
}
This would let you write:
IExecutor<Command> baseExecutor = ....;
IExecutor<SpecialisedCommand> executor = baseExecutor;
This is safe, because you've promised the compiler that the methods on IExecutor
will only ever accept Command
objects, and will never return them.