A couple of years ago, while working on a project, I got a compilation exception related to Generics.
I had a base class
BaseVO and classes deriving from
BaseVO. For example, “
FlightVO : BaseVO”. My idealistic assumption was that since: “FlightVO” inherits from BaseVO, and since BaseVO b = new FlightVO() is valid as per inheritance, then:
List<BaseVO> lstBaseVO = new List<FlightVO>() would also be valid.”
But duh !!! Instead, I get a compilation exception that says:
Argument ’1′: cannot convert from
Since I was so sure about my assumption being right, I posted it on the MSDN forum just to confirm that it was a “bug” :-) in Generics. Thread in MSDN forum: http://social.msdn.microsoft.com/Forums/bg-BG/netfxbcl/thread/e4863bda-3f24-4a28-af55-230baaff5e7b.
Those were the .NET 3.0 days and one of the MVP moderators gave a pretty good explanation as to why my assumption was wrong. Here’s his explanation:
List<Derived> are distinct types, you can’t convert
List<Base>. If it were possible, you could pass a
List<Derived> to a method accepting
List<Base> and that method could add an object of type
Base to the list, which violates the promise that the original list only contains objects of type
I assume someone in the CLR team might have found my assumption quite viable and it got shape in .NET 4.0 as “Covariance and Contravariance in Generics” .
So the above case is a valid example where “Covariance in Generics” can be used.
However, if we compile
List<BaseVO> lstBaseVO = new List<FlightVO>() in .NET 4.0, it would still give the same compilation error for the very explanation given by our MVP friend. In order to make it covariant compliant, our modified piece of code would look like:
IEnumerable< BaseVO > lstBaseVO = new List< FlightVO >();
The above statement is valid because, in .NET 4.0,
List<T> : IEnumerable<out T> where type ‘T’ is marked as covariant.
Here’s a quick summarization about variance in Generics.
- What are the programming constructs where covariance and contravariance are applicable? Variant type parameters are restricted to generic interfaces and generic delegate types thereby ensuring type safety.
- In what scenarios would we want to use variance in Generics? Let's look at a code snippet.
So, covariance and contravariance of generic type parameters enable us to use constructed generic types [A<Athlete>] whose type arguments [Athlete] are more derived (covariance) or less derived (contravariance) than the type argument [Person] of a target constructed type [ISample<Person>].
Covariance seems very natural because it behaves like polymorphism, whereas Contravariance seems counterintuitive. One thing that we need to be clear about is that both Covariance and Contravariance both adhere to the thumb rule of “Inheritance” which says,
“A derived class instance can be assigned to a base class variable”
Now, let’s look at a Contravariance example.
Line 2 of code snippet might make you feel that the thumb rule of inheritance is being violated, i.e., the derived class variable
d is being assigned an instance of the
However, that isn’t the case. Let’s understand each line of the code snippet.
In general, a covariant type parameter can be used as the return type of a generic delegate/interface, and contravariant type parameters can be used as input parameter types.
A brief summary of facts about variance in the common language runtime:
- A generic interface or generic delegate type can have both covariant and contravariant type parameters.
- Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.
- How do we define variant generic interfaces and delegates?
Starting with the .NET Framework 4, Visual Basic and C# have keywords that enable you to mark the generic type parameters of interfaces and delegates as covariant or contravariant.
- A covariant type parameter is marked with the
out keyword. You cannot use a covariant type parameter as a generic type constraint for
interface methods. E.g.,
T Display<out T>(); is an invalid statement.
- A contravariant type parameter is marked with the
in keyword. You can use a contravariant type parameter as a generic type constraint for an
Thank you for reading!