35
loading...
This website collects cookies to deliver better user experience
In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.
IMyInterface<T> {…}
. After having Covariance and Contravariance introduced, you can now follow the pattern public interface IMyInterface<out T> {…}
or public interface IMyInterface<in T> {…}
.IEnumerable<out T>
?IComparable<in T>
?Class A has F1()
defined.
Class B has F1()
and F2()
defined.
Class C has F1()
, F2()
, and F3()
defined.
The interface IReaderWriter
has Read()
which returns an object of type TEntity
and Write(TEntity entity)
which expects a parameter of type TEntity
.
TestReadWriter()
method as follows:TestReadWriter()
is already expecting a parameter of type IReaderWriter<B>
.param.Read()
would return an instance of class A, not B
=> So, the var b
would actually be of type A, not B
=> This would lead to the b.F2()
line to fail as the var b
-which is actually of type A- does not have F2()
defined
param.Write()
line in the code above would be expecting to receive a parameter of type A, not B
=> So, calling param.Write()
while passing in a parameter of type B would both work fine
TestReadWriter()
with passing in an instance of IReaderWriter<A>
.param.Read()
would return an instance of class C, not B
=> So, the var b
would actually be of type C, not B
=> This would lead to the b.F2()
line to work fine as the var b
would have F2()
param.Write()
line in the code above would be expecting to receive a parameter of type C, not B
=> So, calling param.Write()
while passing in a parameter of type B would fail because simply you can’t replace C with its parent B
TestReadWriter()
with passing in an instance of IReaderWriter<C>
.Calling TestReadWriter(IReaderWriter<B> param)
when passing in an instance of IReaderWriter<B>
is always fine.
Calling TestReadWriter(IReaderWriter<B> param)
when passing in an instance of IReaderWriter<A>
would be fine if we don’t have the param.Read()
call.
Calling TestReadWriter(IReaderWriter<B> param)
when passing in an instance of IReaderWriter<C>
would be fine if we don’t have the param.Write()
call.
However, since we always have a mix between param.Read()
and param.Write()
, we would always have to stick to calling TestReadWriter(IReaderWriter<B> param)
with passing in an instance of IReaderWriter<B>
, nothing else.
Unless…….
IReaderWriter<TEntity>
interface defines either TEntity Read()
or void Write(TEntity entity)
, not both of them at the same time.TEntity Read()
, we would be able to call TestReadWriter(IReaderWriter<B> param)
with passing in an instance of IReaderWriter<A>
or IReaderWriter<B>
.void Write(TEntity entity)
, we would be able to call TestReadWriter(IReaderWriter<B> param)
with passing in an instance of IReaderWriter<B>
or IReaderWriter<C>
.In the real world, the compiler -in design time- would never allow calling TestReadWriter(IReaderWriter<B> param)
with passing in an instance of IReaderWriter<A>
. You would get a compilation error.
Also, the compiler -in design time- would not allow calling TestReadWriter(IReaderWriter<B> param)
with passing in an instance of IReaderWriter<C>
. You would get a compilation error.
From point #1 and #2, this is called Invariance.
Even if you drop the TEntity Read()
from the IReaderWriter<TEntity>
interface, the compiler -in design time- would not allow you to call TestReadWriter(IReaderWriter<B> param)
with passing in an instance of IReaderWriter<A>
. You would get a compilation error. This is because the compiler would not -implicitly by itself- look into the members defined into the interface and see if it is going to always work at runtime or not. You will need to do this by yourself through <in TEntity>
. This acts as a promise from you to the compiler that all members in the interface would either don’t depend on TEntity
or deal with it as an input, not an output. This is called Contravariance.
Similarly, even if you drop the void Write(TEntity entity)
from the IReaderWriter<TEntity>
interface, the compiler -in design time- would not allow you to call TestReadWriter(IReaderWriter<B> param)
with passing in an instance of IReaderWriter<C>
. You would get a compilation error. This is because the compiler would not -implicitly by itself- look into the members defined into the interface and see if it is going to always work at runtime or not. You will need to do this by yourself through <out TEntity>
. This acts as a promise from you to the compiler that all members in the interface would either don’t depend on TEntity
or deal with it as an output, not an input. This is called Covariance.
Therefore, adding <out >
or <in >
makes the compiler less restrictive to serve our needs, not more restrictive as some developers would think.
Mix between input and output generic type => Invariance => the most restrictive => can’t replace with parents or children.
Added <in >
=> only input => Contravariance => itself or replace with parents.
Added <out >
=> only output => Covariance => itself or replace with children.