I’m having some issues with, I think, variance, which I don’t fully understand. I have a generic interface with two type parameters, like this:
public interface IInvoker<TParameter, TResult> {
TResult Invoke(TParameter parameter);
}
Now, in my case, I want to let TA and TB be abstract classes, something like this:
public abstract class AbstractParameter {
public int A { get; set; }
}
public abstract class AbstractResult {
public string X { get; set; }
}
public class Parameter1 : AbstractParameter {
public int B { get; set; }
}
public class Result1 : AbstractResult {
public string Y { get; set; }
}
// ... Many more types
I then want to process a set of different implementations of IInvoker<,>, so I figured I could do something like this
public class InvokerOne : IInvoker<Parameter1, Result1> { /* ... */ }
public class InvokerTwo : IInvoker<Parameter2, Result2> { /* ... */ }
// ..
IInvoker<AbstractParameter, AbstractResult>[] invokers = { new InvokerOne(), new InvokerTwo() };
This does not work, because IInvoker<AbstractParameter, AbstractResult> cannot be assigned from IInvoker<Parameter1, Result1> (and friends), as far as I understand. First I figured this was the time to slap some in and out on my interface (interface IInvoker<in TParameter, out TResult>), but that did not help.
But I don’t understand why? As far as I can tell, anyone using an IInvoker<AbstractParameter, AbstractResult> should be able to call Invoke, right? What am I missing?
The problem is the
TResulttype parameter is contra-variant, but you are trying to use them co-variantly in your assignment e.g.TResultis co-variant, so it’s ok forAbstractResultto be a larger type thanResult1. However, sinceTParameteris contra-variant,TParametermust be a smaller type thanParameter1, which is not the case forAbstractParameter.If the above was valid you could do:
which is not safe.
You could have the following however:
here
OtherParameter1can be passed as a parameter toInvokesince it will always be a valid forParameter1.