0

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.

J Lewis
  • 462
  • 1
  • 4
  • 15
  • 1
    Did you want `interface IExecutor where T : Command { void Execute(T command); }`? Although in your example, you can just do `interface IExecutor { void Execute(Command command); }` (note that this isn't covariance. Interface covariance lets you write `IEnumerable x = new List()`, which is something a bit different) – canton7 May 24 '19 at 12:10
  • That first one sounds right. Is there a meaningful difference between using covariance and using the `where` statement? – J Lewis May 24 '19 at 12:13
  • 1
    Covariance is something a bit different. `IEnumerable` is declared as `IEnumerable { ... }`, which lets you write e.g. `IEnumerable x = new List()` - i.e. it tells the compiler that it's safe to refer to a `List` as a collection of any old objects, because you can only ever take items out of the collection, and not put them in. – canton7 May 24 '19 at 12:14
  • I think that makes sense? In any case your solution looks like what I'm after. I'd happily accept it as an answer – J Lewis May 24 '19 at 12:16

1 Answers1

2

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.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • Covariance is kinda OOP concept, it's not unique to C# – Coderino Javarino May 24 '19 at 12:21
  • @CoderinoJavarino I know. That's why I said "Covariance in C# is..." - I'm saying what covariance means in C# specifically, as defined by the C# documentation. Updated the wording to hopefully be clearer. – canton7 May 24 '19 at 12:22