One thing you're missing is the variance declaration in your interface. The interface is not variant unless you declare it to be:
public interface IInvoker<in TParameter, out TResult>
// ^^ ^^^
// Look! Here too!
{
TResult Invoke(TParameter parameter);
}
The in
and out
keywords help underscore the nature of the variance. The type is contravariant with respect to the in
parameter and covariant with respect to the out
parameter. In other words, you can do this, assuming the usual Animal : Mammal : Cat
example:
IInvoker<Mammal, Mammal> a = Whatever();
IInvoker<Cat, Mammal> b = a;
IInvoker<Mammal, Animal> c = a;
That's not particularly useful by itself, but the point is that you can use the IInvoker<Mammal, Mammal>
anywhere you need an IInvoker<Cat, Mammal>
or an IInvoker<Mammal, Animal>
.
There's also something important missing from your question: What exactly do you want to do with your set of IInvoker<,>
implementations? ("I want to process a set of different implementations of IInvoker<,>
....") The answer to this question will lead you to your solution. Do you want to invoke them all with some objects inheriting from AbstractParameter? If so, as Lee explains, you'd have some trouble if you could do what you want, since nothing would prevent this:
IInvoker<AbstractParameter, AbstractResult>[] invokers = { new InvokerOne(), new InvokerTwo() };
AbstractParameter[] parameters = { new ParameterOne(), new ParameterTwo() };
AbstractResult[] results = { invokers[0].Invoke(parameters[1] /* oops */), invokers[1].Invoke(parameters[0] /* oops */) };
One way to solve that problem would be to remove the parameter from the interface. Make it a private field of the invoker, or else make a class that pairs invokers with their parameters, something like this:
interface IInvokerParameterPair<out TResult>()
where TResult : AbstractResult
{
TResult InvokeTheInvoker();
}
class InvokerParameterPair<TParameter, TResult> : IInvokerParameterPair<TResult>
where TParameter : AbstractParameter
where TResult : AbstractResult
{
private IInvoker<TParameter, TResult> _invoker;
private TParameter _parameter;
public InvokerParameterPair(IInvoker<TParameter, TResult> invoker, TParameter parameter)
{
_invoker = invoker;
_parameter = parameter;
}
public TResult InvokeTheInvoker()
{
return _invoker.Invoke(_parameter);
}
}
If, on the other hand, you would like to do some processing that has nothing to do with the Invoke method, then your invokers should implement some other common interface or inherit from some other common base class, like this:
public interface IProcessable { }
public interface IInvoker<in TParameter, out TResult> : IProcessable
{
TResult Invoke(TParameter parameter);
}
public class InvokerOne : IInvoker<Parameter1, Result1> { /* ... */ }
public class InvokerTwo : IInvoker<Parameter2, Result2> { /* ... */ }
IProcessable[] invokers = { new InvokerOne(), new InvokerTwo() };
or this:
public interface IInvoker<in TParameter, out TResult> : IProcessable
{
TResult Invoke(TParameter parameter);
}
public abstract class Processable { }
public class InvokerOne : Processable, IInvoker<Parameter1, Result1> { /* ... */ }
public class InvokerTwo : Processable, IInvoker<Parameter2, Result2> { /* ... */ }
Processable[] invokers = { new InvokerOne(), new InvokerTwo() };