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 TResult
type parameter is contra-variant, but you are trying to use them co-variantly in your assignment e.g.
IInvoker<AbstractParameter, AbstractResult> i1 = new InvokerOne();
TResult
is co-variant, so it's ok for AbstractResult
to be a larger type than Result1
. However, since TParameter
is contra-variant, TParameter
must be a smaller type than Parameter1
, which is not the case for AbstractParameter
.
If the above was valid you could do:
class OtherParameter : AbstractParameter { ... };
IInvoker<AbstractParameter, AbstractResult> i1 = new InvokerOne();
i1.Invoke(new OtherParameter());
which is not safe.
You could have the following however:
public class OtherParameter1 : Parameter1 { }
IInvoker<OtherParameter1, AbstractResult> i1 = new InvokerOne();
here OtherParameter1
can be passed as a parameter to Invoke
since it will always be a valid for Parameter1
.